From 0630576de1a9f12e3bf129711d688867798d4a43 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 4 Oct 2024 16:12:29 +1000 Subject: [PATCH 01/21] Refactor custom status to be static --- class-workflow.php | 2 +- modules/custom-status/custom-status.php | 515 ++++++++++-------- .../custom-status/meta/position-handler.php | 8 +- .../meta/required-metadata-id-handler.php | 13 +- .../meta/required-user-id-handler.php | 17 +- .../rest/custom-status-endpoint.php | 41 +- .../editorial-metadata/editorial-metadata.php | 2 +- modules/preview/preview.php | 5 +- modules/preview/rest/preview-endpoint.php | 4 +- modules/preview/token.php | 4 +- modules/settings/settings.php | 2 +- modules/shared/php/class-module.php | 110 ---- .../test-required-metadata-id-handler.php | 10 +- .../meta/test-required-user-id-handler.php | 16 +- .../rest/test-custom-status-endpoint.php | 38 +- .../test-custom-status-transitions.php | 26 +- vip-workflow.php | 1 + 17 files changed, 377 insertions(+), 437 deletions(-) diff --git a/class-workflow.php b/class-workflow.php index 42dba3d6..449565c3 100644 --- a/class-workflow.php +++ b/class-workflow.php @@ -75,7 +75,7 @@ private function load_modules() { // VIP Workflow base module require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/class-module.php'; - $skip_module_dirs = [ 'shared', 'preview', 'notifications', 'editorial-metadata' ]; + $skip_module_dirs = [ 'shared', 'preview', 'notifications', 'editorial-metadata', 'custom-status' ]; // Scan the modules directory and include any modules that exist there $module_dirs = scandir( VIP_WORKFLOW_ROOT . '/modules/' ); diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 9b5c5bde..4843d5b7 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -14,9 +14,10 @@ require_once __DIR__ . '/meta/required-metadata-id-handler.php'; require_once __DIR__ . '/meta/position-handler.php'; + +use VIPWorkflow\Modules\Shared\PHP\InstallUtilities; use VIPWorkflow\Modules\CustomStatus\REST\CustomStatusEndpoint; use VIPWorkflow\VIP_Workflow; -use VIPWorkflow\Modules\Shared\PHP\Module; use WP_Error; use WP_Query; @@ -24,11 +25,7 @@ use WP_Term; use WP_Post; -class Custom_Status extends Module { - - public $module; - - private $custom_statuses_cache = []; +class CustomStatus { // This is taxonomy name used to store all our custom statuses const TAXONOMY_KEY = 'vw_post_status'; @@ -41,116 +38,63 @@ class Custom_Status extends Module { const METADATA_REQ_USER_IDS_KEY = 'required_user_ids'; const METADATA_REQ_USERS_KEY = 'required_users'; - /** - * Register the module with VIP Workflow but don't do anything else - */ - public function __construct() { - - $this->module_url = $this->get_module_url( __FILE__ ); - // Register the module with VIP Workflow - $args = [ - 'module_url' => $this->module_url, - 'slug' => 'custom-status', - 'configure_page_cb' => 'print_configure_view', - ]; - $this->module = VIP_Workflow::instance()->register_module( 'custom_status', $args ); - } + private static $published_statuses = array( + 'publish', + 'future', + 'private', + ); - /** - * Initialize the Custom_Status class if the module is active - */ - public function init() { - // Register the custom status taxonomy - $this->register_custom_status_taxonomy(); + private static $custom_statuses_cache = []; + + public static function init(): void { + // Register the taxonomy we use with WordPress core + add_action( 'init', [ __CLASS__, 'register_custom_status_taxonomy' ] ); // Register the custom statuses in core - $this->register_custom_statuses(); + add_action( 'init', [ __CLASS__, 'register_custom_statuses' ] ); + + // Setup custom statuses on first install + add_action( 'init', [ __CLASS__, 'setup_install' ] ); // Register our settings - if ( ! $this->disable_custom_statuses_for_post_type() ) { + if ( ! self::disable_custom_statuses_for_post_type() ) { // Load CSS and JS resources for the admin page - add_action( 'admin_enqueue_scripts', [ $this, 'action_admin_enqueue_scripts' ] ); + add_action( 'admin_enqueue_scripts', [ __CLASS__, 'action_admin_enqueue_scripts' ] ); // Assets for block editor UI. - add_action( 'enqueue_block_editor_assets', [ $this, 'load_scripts_for_block_editor' ] ); + add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'load_scripts_for_block_editor' ] ); // Assets for iframed block editor and editor UI. - add_action( 'enqueue_block_editor_assets', [ $this, 'load_styles_for_block_editor' ] ); + add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'load_styles_for_block_editor' ] ); } - add_action( 'admin_print_scripts', [ $this, 'post_admin_header' ] ); + add_action( 'admin_print_scripts', [ __CLASS__, 'post_admin_header' ] ); // Add custom statuses to the post states. - add_filter( 'display_post_states', [ $this, 'add_status_to_post_states' ], 10, 2 ); + add_filter( 'display_post_states', [ __CLASS__, 'add_status_to_post_states' ], 10, 2 ); // Register sidebar menu - add_action( 'admin_menu', [ $this, 'add_admin_menu' ], 6 /* Prior to default registration of sub-pages */ ); + add_action( 'admin_menu', [ __CLASS__, 'add_admin_menu' ], 6 /* Prior to default registration of sub-pages */ ); // These seven-ish methods are hacks for fixing bugs in WordPress core - add_filter( 'wp_insert_post_data', [ $this, 'maybe_keep_post_name_empty' ], 10, 2 ); - add_filter( 'pre_wp_unique_post_slug', [ $this, 'fix_unique_post_slug' ], 10, 6 ); - add_filter( 'preview_post_link', [ $this, 'fix_preview_link_part_one' ] ); - add_filter( 'post_link', [ $this, 'fix_preview_link_part_two' ], 10, 3 ); - add_filter( 'page_link', [ $this, 'fix_preview_link_part_two' ], 10, 3 ); - add_filter( 'post_type_link', [ $this, 'fix_preview_link_part_two' ], 10, 3 ); - add_filter( 'preview_post_link', [ $this, 'fix_preview_link_part_three' ], 11, 2 ); - add_filter( 'get_sample_permalink', [ $this, 'fix_get_sample_permalink' ], 10, 5 ); - add_filter( 'get_sample_permalink_html', [ $this, 'fix_get_sample_permalink_html' ], 10, 5 ); - add_filter( 'post_row_actions', [ $this, 'fix_post_row_actions' ], 10, 2 ); - add_filter( 'page_row_actions', [ $this, 'fix_post_row_actions' ], 10, 2 ); + add_filter( 'wp_insert_post_data', [ __CLASS__, 'maybe_keep_post_name_empty' ], 10, 2 ); + add_filter( 'pre_wp_unique_post_slug', [ __CLASS__, 'fix_unique_post_slug' ], 10, 6 ); + add_filter( 'preview_post_link', [ __CLASS__, 'fix_preview_link_part_one' ] ); + add_filter( 'post_link', [ __CLASS__, 'fix_preview_link_part_two' ], 10, 3 ); + add_filter( 'page_link', [ __CLASS__, 'fix_preview_link_part_two' ], 10, 3 ); + add_filter( 'post_type_link', [ __CLASS__, 'fix_preview_link_part_two' ], 10, 3 ); + add_filter( 'preview_post_link', [ __CLASS__, 'fix_preview_link_part_three' ], 11, 2 ); + add_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); + add_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); + add_filter( 'post_row_actions', [ __CLASS__, 'fix_post_row_actions' ], 10, 2 ); + add_filter( 'page_row_actions', [ __CLASS__, 'fix_post_row_actions' ], 10, 2 ); // Pagination for custom post statuses when previewing posts - add_filter( 'wp_link_pages_link', [ $this, 'modify_preview_link_pagination_url' ], 10, 2 ); + add_filter( 'wp_link_pages_link', [ __CLASS__, 'modify_preview_link_pagination_url' ], 10, 2 ); // Add server-side controls to block post status movements that are prohihibited by workflow rules - add_filter( 'wp_insert_post_data', [ $this, 'maybe_block_post_update' ], 1000, 4 ); - add_filter( 'user_has_cap', [ $this, 'remove_or_add_publish_capability_for_user' ], 10, 3 ); - } - - /** - * Create the default set of custom statuses the first time the module is loaded - */ - public function install() { - - $default_terms = [ - [ - 'name' => __( 'Pitch', 'vip-workflow' ), - 'slug' => 'pitch', - 'description' => __( 'Idea proposed; waiting for acceptance.', 'vip-workflow' ), - 'position' => 1, - ], - [ - 'name' => __( 'Assigned', 'vip-workflow' ), - 'slug' => 'assigned', - 'description' => __( 'Post idea assigned to writer.', 'vip-workflow' ), - 'position' => 2, - ], - [ - 'name' => __( 'In Progress', 'vip-workflow' ), - 'slug' => 'in-progress', - 'description' => __( 'Writer is working on the post.', 'vip-workflow' ), - 'position' => 3, - ], - [ - 'name' => __( 'Draft', 'vip-workflow' ), - 'slug' => 'draft', - 'description' => __( 'Post is a draft; not ready for review or publication.', 'vip-workflow' ), - 'position' => 4, - ], - [ - 'name' => __( 'Pending Review' ), - 'slug' => 'pending', - 'description' => __( 'Post needs to be reviewed by an editor.', 'vip-workflow' ), - 'position' => 5, - ], - ]; - - // Okay, now add the default statuses to the db if they don't already exist - foreach ( $default_terms as $term ) { - if ( ! term_exists( $term['slug'], self::TAXONOMY_KEY ) ) { - $this->add_custom_status( $term ); - } - } + add_filter( 'wp_insert_post_data', [ __CLASS__, 'maybe_block_post_update' ], 1000, 4 ); + add_filter( 'user_has_cap', [ __CLASS__, 'remove_or_add_publish_capability_for_user' ], 10, 3 ); } /** @@ -178,14 +122,14 @@ public static function register_custom_status_taxonomy(): void { * Makes the call to register_post_status to register the user's custom statuses. * Also unregisters pending, in case the user doesn't want them. */ - public function register_custom_statuses() { + public static function register_custom_statuses(): void { global $wp_post_statuses; // Users can delete the pending status if they want, so let's get rid of that // It'll get re-added if the user hasn't "deleted" them unset( $wp_post_statuses['pending'] ); - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); // Unfortunately, register_post_status() doesn't accept a // post type argument, so we have to register the post @@ -211,7 +155,7 @@ public function register_custom_statuses() { * * @return bool */ - public function disable_custom_statuses_for_post_type( $post_type = null ) { + public static function disable_custom_statuses_for_post_type( ?string $post_type = null ): bool { global $pagenow; // Only allow deregistering on 'edit.php' and 'post.php' @@ -220,7 +164,7 @@ public function disable_custom_statuses_for_post_type( $post_type = null ) { } if ( is_null( $post_type ) ) { - $post_type = $this->get_current_post_type(); + $post_type = self::get_current_post_type(); } $supported_post_types = VIP_Workflow::instance()->get_supported_post_types(); @@ -232,20 +176,110 @@ public function disable_custom_statuses_for_post_type( $post_type = null ) { return false; } - public function add_admin_menu() { + /** + * Checks for the current post type + * + * @return string|null $post_type The post type we've found, or null if no post type + */ + public static function get_current_post_type(): ?string { + global $post, $typenow, $pagenow, $current_screen; + //get_post() needs a variable + $post_id = isset( $_REQUEST['post'] ) ? (int) $_REQUEST['post'] : false; + + if ( $post && $post->post_type ) { + $post_type = $post->post_type; + } elseif ( $typenow ) { + $post_type = $typenow; + } elseif ( $current_screen && ! empty( $current_screen->post_type ) ) { + $post_type = $current_screen->post_type; + } elseif ( isset( $_REQUEST['post_type'] ) ) { + $post_type = sanitize_key( $_REQUEST['post_type'] ); + } elseif ( 'post.php' == $pagenow + && $post_id + && ! empty( get_post( $post_id )->post_type ) ) { + $post_type = get_post( $post_id )->post_type; + } elseif ( 'edit.php' == $pagenow && empty( $_REQUEST['post_type'] ) ) { + $post_type = 'post'; + } else { + $post_type = null; + } + + return $post_type; + } + + /** + * Load default custom statuses the first time the module is loaded + * + * @access private + */ + public static function setup_install(): void { + InstallUtilities::install_if_first_run( self::SETTINGS_SLUG, function () { + $default_terms = [ + [ + 'name' => __( 'Pitch', 'vip-workflow' ), + 'slug' => 'pitch', + 'description' => __( 'Idea proposed; waiting for acceptance.', 'vip-workflow' ), + 'position' => 1, + ], + [ + 'name' => __( 'Assigned', 'vip-workflow' ), + 'slug' => 'assigned', + 'description' => __( 'Post idea assigned to writer.', 'vip-workflow' ), + 'position' => 2, + ], + [ + 'name' => __( 'In Progress', 'vip-workflow' ), + 'slug' => 'in-progress', + 'description' => __( 'Writer is working on the post.', 'vip-workflow' ), + 'position' => 3, + ], + [ + 'name' => __( 'Draft', 'vip-workflow' ), + 'slug' => 'draft', + 'description' => __( 'Post is a draft; not ready for review or publication.', 'vip-workflow' ), + 'position' => 4, + ], + [ + 'name' => __( 'Pending Review' ), + 'slug' => 'pending', + 'description' => __( 'Post needs to be reviewed by an editor.', 'vip-workflow' ), + 'position' => 5, + ], + ]; + + // Add the custom statuses if the slugs don't conflict + foreach ( $default_terms as $term ) { + if ( ! term_exists( $term['slug'], self::TAXONOMY_KEY ) ) { + self::add_custom_status( $term ); + } + } + }); + } + + /** + * Register admin sidebar menu + * + * @access private + */ + public static function add_admin_menu(): void { $menu_title = __( 'VIP Workflow', 'vip-workflow' ); - add_menu_page( $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ $this, 'render_settings_view' ] ); + add_menu_page( $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ __CLASS__, 'render_settings_view' ] ); } - public function configure_page_cb() { - // do nothing + /** + * Primary configuration page for custom status class, which is also the main entry point for configuring the plugin + */ + public static function render_settings_view(): void { + include_once __DIR__ . '/views/manage-workflow.php'; } /** * Enqueue resources that we need in the admin settings page + * + * @access private */ - public function action_admin_enqueue_scripts() { + public static function action_admin_enqueue_scripts(): void { // Load Javascript we need to use on the configuration views if ( VIP_Workflow::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status-configure.asset.php'; @@ -253,7 +287,7 @@ public function action_admin_enqueue_scripts() { wp_enqueue_style( 'vip-workflow-custom-status-styles', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-configure.css', [ 'wp-components' ], $asset_file['version'] ); wp_localize_script( 'vip-workflow-custom-status-configure', 'VW_CUSTOM_STATUS_CONFIGURE', [ - 'custom_statuses' => $this->get_custom_statuses(), + 'custom_statuses' => self::get_custom_statuses(), 'editorial_metadatas' => EditorialMetadata::get_editorial_metadata_terms(), 'url_edit_status' => CustomStatusEndpoint::get_crud_url(), 'url_reorder_status' => CustomStatusEndpoint::get_reorder_url(), @@ -261,7 +295,7 @@ public function action_admin_enqueue_scripts() { } // Custom javascript to modify the post status dropdown where it shows up - if ( $this->is_whitelisted_page() ) { + if ( self::is_whitelisted_page() ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status.asset.php'; $dependencies = [ ...$asset_file['dependencies'], 'jquery', 'post' ]; wp_enqueue_script( 'vip_workflow-custom_status', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status.js', $dependencies, $asset_file['version'], true ); @@ -278,7 +312,13 @@ public function action_admin_enqueue_scripts() { } } - public function load_scripts_for_block_editor() { + + /** + * Enqueue resources that we need in the admin settings page + * + * @access private + */ + public static function load_scripts_for_block_editor(): void { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status-block.asset.php'; wp_enqueue_script( 'vip-workflow-block-custom-status-script', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-block.js', $asset_file['dependencies'], $asset_file['version'], true ); @@ -287,12 +327,17 @@ public function load_scripts_for_block_editor() { wp_localize_script( 'vip-workflow-block-custom-status-script', 'VW_CUSTOM_STATUSES', [ 'current_user_id' => get_current_user_id(), 'is_publish_guard_enabled' => $publish_guard_enabled, - 'status_terms' => $this->get_custom_statuses(), + 'status_terms' => self::get_custom_statuses(), 'supported_post_types' => VIP_Workflow::instance()->get_supported_post_types(), ] ); } - public function load_styles_for_block_editor() { + /** + * Enqueue resources that we need in the block editor + * + * @access private + */ + public static function load_styles_for_block_editor(): void { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status-block.asset.php'; wp_enqueue_style( 'vip-workflow-custom-status-styles', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-block.css', [], $asset_file['version'] ); @@ -301,14 +346,16 @@ public function load_styles_for_block_editor() { /** * Check whether custom status stuff should be loaded on this page */ - public function is_whitelisted_page() { + public static function is_whitelisted_page(): bool { global $pagenow; - if ( ! in_array( $this->get_current_post_type(), VIP_Workflow::instance()->get_supported_post_types() ) ) { + $current_post_type = self::get_current_post_type(); + + if ( ! in_array( $current_post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { return false; } - $post_type_obj = get_post_type_object( $this->get_current_post_type() ); + $post_type_obj = get_post_type_object( $current_post_type ); if ( ! current_user_can( $post_type_obj->cap->edit_posts ) ) { return false; @@ -321,20 +368,20 @@ public function is_whitelisted_page() { /** * Adds all necessary javascripts to make custom statuses work */ - public function post_admin_header() { + public static function post_admin_header(): void { global $post, $pagenow; - if ( $this->disable_custom_statuses_for_post_type() ) { + if ( self::disable_custom_statuses_for_post_type() ) { return; } - // Get current user + // Set the current user, so we can check if they can publish posts wp_get_current_user(); // Only add the script to Edit Post and Edit Page pages -- don't want to bog down the rest of the admin with unnecessary javascript - if ( $this->is_whitelisted_page() ) { + if ( self::is_whitelisted_page() ) { - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); // $selected can be empty, but must be set because it's used as a JS variable $selected = ''; @@ -377,7 +424,7 @@ public function post_admin_header() { ]; } - $post_type_obj = get_post_type_object( $this->get_current_post_type() ); + $post_type_obj = get_post_type_object( self::get_current_post_type() ); // Now, let's print the JS vars ?> @@ -397,8 +444,8 @@ public function post_admin_header() { * * @return array $allcaps All capabilities for the user */ - public function maybe_block_post_update( $data ) { - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + public static function maybe_block_post_update( array $data ): array|bool { + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); // Ignore if it's not a post status and post type we support if ( ! in_array( $data['post_type'], VIP_Workflow::instance()->get_supported_post_types() ) ) { @@ -420,7 +467,7 @@ public function maybe_block_post_update( $data ) { return $data; } - $prior_post_status = $this->get_custom_status_by( 'slug', $prior_post_status_slug ); + $prior_post_status = self::get_custom_status_by( 'slug', $prior_post_status_slug ); $required_user_ids = $prior_post_status->meta[ self::METADATA_REQ_USER_IDS_KEY ] ?? []; if ( $required_user_ids && ! in_array( get_current_user_id(), $required_user_ids, true ) ) { @@ -441,7 +488,7 @@ public function maybe_block_post_update( $data ) { * * @return array $allcaps All capabilities for the user */ - public function remove_or_add_publish_capability_for_user( $allcaps, $cap, $args ) { + public static function remove_or_add_publish_capability_for_user( array $allcaps, array $cap, array $args ): array { global $post; $supported_publish_caps_map = [ @@ -465,7 +512,7 @@ public function remove_or_add_publish_capability_for_user( $allcaps, $cap, $args return $allcaps; } - $custom_statuses = VIP_Workflow::instance()->custom_status->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); $status_slugs = wp_list_pluck( $custom_statuses, 'slug' ); // Bail early if the post is not using a custom status @@ -491,7 +538,7 @@ public function remove_or_add_publish_capability_for_user( $allcaps, $cap, $args * @param string $meta The metadata that failed to save * @return WP_Error The WP_Error object */ - private function generate_error_and_delete_bad_data( int $term_id, string $meta ): WP_Error { + private static function generate_error_and_delete_bad_data( int $term_id, string $meta ): WP_Error { // Trigger the deletion of the metadata associated with the status do_action( 'vw_delete_custom_status_meta', $term_id ); wp_delete_term( $term_id, self::TAXONOMY_KEY ); @@ -519,10 +566,10 @@ private function generate_error_and_delete_bad_data( int $term_id, string $meta * * @return object|WP_Error $inserted_term The newly inserted term object or a WP_Error object */ - public function add_custom_status( array $args ): WP_Term|WP_Error { + public static function add_custom_status( array $args ): WP_Term|WP_Error { if ( ! isset( $args['position'] ) ) { // get the existing statuses, ordered by position - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); // get the last status position $last_position = $custom_statuses[ array_key_last( $custom_statuses ) ]->meta[ self::METADATA_POSITION_KEY ]; @@ -545,7 +592,7 @@ public function add_custom_status( array $args ): WP_Term|WP_Error { } // Reset our internal object cache - $this->custom_statuses_cache = []; + self::$custom_statuses_cache = []; $term_id = $inserted_term['term_id']; @@ -557,20 +604,20 @@ public function add_custom_status( array $args ): WP_Term|WP_Error { $position_meta_result = update_term_meta( $term_id, self::METADATA_POSITION_KEY, $position ); if ( is_wp_error( $position_meta_result ) ) { - return $this->generate_error_and_delete_bad_data( $term_id, 'position' ); + return self::generate_error_and_delete_bad_data( $term_id, 'position' ); } $required_metadata_ids_result = update_term_meta( $term_id, self::METADATA_REQ_EDITORIAL_IDS_KEY, $required_metadata_ids ); if ( is_wp_error( $required_metadata_ids_result ) ) { - return $this->generate_error_and_delete_bad_data( $term_id, 'required editorial metadata fields' ); + return self::generate_error_and_delete_bad_data( $term_id, 'required editorial metadata fields' ); } $required_user_ids_result = update_term_meta( $term_id, self::METADATA_REQ_USER_IDS_KEY, $required_user_ids ); if ( is_wp_error( $required_user_ids_result ) ) { - return $this->generate_error_and_delete_bad_data( $term_id, 'required users' ); + return self::generate_error_and_delete_bad_data( $term_id, 'required users' ); } - $term_result = $this->get_custom_status_by( 'id', $term_id ); + $term_result = self::get_custom_status_by( 'id', $term_id ); return $term_result; } @@ -582,8 +629,8 @@ public function add_custom_status( array $args ): WP_Term|WP_Error { * @param array $args Any arguments to be updated * @return object $updated_status Newly updated status object */ - public function update_custom_status( int $status_id, array $args = [] ): WP_Term|WP_Error { - $old_status = $this->get_custom_status_by( 'id', $status_id ); + public static function update_custom_status( int $status_id, array $args = [] ): WP_Term|WP_Error { + $old_status = self::get_custom_status_by( 'id', $status_id ); if ( is_wp_error( $old_status ) ) { return $old_status; } else if ( ! $old_status ) { @@ -591,7 +638,7 @@ public function update_custom_status( int $status_id, array $args = [] ): WP_Ter } // Reset our internal object cache - $this->custom_statuses_cache = []; + self::$custom_statuses_cache = []; // Prevent user from changing draft name or slug if ( 'draft' === $old_status->slug @@ -609,9 +656,9 @@ public function update_custom_status( int $status_id, array $args = [] ): WP_Ter } // Reassign posts to new status slug if the slug changed and isn't restricted - if ( isset( $args['slug'] ) && $args['slug'] != $old_status->slug && ! $this->is_restricted_status( $old_status->slug ) ) { + if ( isset( $args['slug'] ) && $args['slug'] != $old_status->slug && ! self::is_restricted_status( $old_status->slug ) ) { $new_status = $args['slug']; - $reassigned_result = $this->reassign_post_status( $old_status->slug, $new_status ); + $reassigned_result = self::reassign_post_status( $old_status->slug, $new_status ); // If the reassignment failed, return the error if ( is_wp_error( $reassigned_result ) ) { return $reassigned_result; @@ -650,13 +697,13 @@ public function update_custom_status( int $status_id, array $args = [] ): WP_Ter $updated_term = wp_update_term( $status_id, self::TAXONOMY_KEY, $term_fields_to_update ); // Reset status cache again, as reassign_post_status() will recache prior statuses - $this->custom_statuses_cache = []; + self::$custom_statuses_cache = []; if ( is_wp_error( $updated_term ) ) { return $updated_term; } - $status_result = $this->get_custom_status_by( 'id', $status_id ); + $status_result = self::get_custom_status_by( 'id', $status_id ); return $status_result; } @@ -667,9 +714,9 @@ public function update_custom_status( int $status_id, array $args = [] ): WP_Ter * Partly a wrapper for the wp_delete_term function. * BUT, also reassigns posts that currently have the deleted status assigned. */ - public function delete_custom_status( int $status_id ): bool|WP_Error { + public static function delete_custom_status( int $status_id ): bool|WP_Error { // Get slug for the old status - $old_status = $this->get_custom_status_by( 'id', $status_id ); + $old_status = self::get_custom_status_by( 'id', $status_id ); if ( is_wp_error( $old_status ) ) { return $old_status; } else if ( ! $old_status ) { @@ -678,23 +725,23 @@ public function delete_custom_status( int $status_id ): bool|WP_Error { $old_status_slug = $old_status->slug; - if ( $this->is_restricted_status( $old_status_slug ) || 'draft' === $old_status_slug ) { + if ( self::is_restricted_status( $old_status_slug ) || 'draft' === $old_status_slug ) { return new WP_Error( 'restricted', __( 'Restricted status ', 'vip-workflow' ) . '(' . $old_status->name . ')' ); } // Reset our internal object cache - $this->custom_statuses_cache = []; + self::$custom_statuses_cache = []; // Get the new status to reassign posts to, which would be the first custom status. // In the event that the first custom status is being deleted, we'll reassign to the second custom status. // Since draft cannot be deleted, we don't need to worry about ever getting index out of bounds. - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); $new_status_slug = $custom_statuses[0]->slug; if ( $old_status_slug === $new_status_slug ) { $new_status_slug = $custom_statuses[1]->slug; } - $reassigned_result = $this->reassign_post_status( $old_status_slug, $new_status_slug ); + $reassigned_result = self::reassign_post_status( $old_status_slug, $new_status_slug ); // If the reassignment failed, return the error if ( is_wp_error( $reassigned_result ) ) { return $reassigned_result; @@ -709,16 +756,16 @@ public function delete_custom_status( int $status_id ): bool|WP_Error { } // Reset status cache again, as reassign_post_status() will recache prior statuses - $this->custom_statuses_cache = []; + self::$custom_statuses_cache = []; // Re-order the positions after deletion - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); $current_postition = 1; // save each status with the new position foreach ( $custom_statuses as $status ) { - $this->update_custom_status( $status->term_id, [ 'position' => $current_postition ] ); + self::update_custom_status( $status->term_id, [ 'position' => $current_postition ] ); ++$current_postition; } @@ -732,14 +779,14 @@ public function delete_custom_status( int $status_id ): bool|WP_Error { * @param array|string $statuses * @return array $statuses All of the statuses */ - public function get_custom_statuses(): array { - if ( $this->disable_custom_statuses_for_post_type() ) { - return $this->get_core_post_statuses(); + public static function get_custom_statuses(): array { + if ( self::disable_custom_statuses_for_post_type() ) { + return self::get_core_post_statuses(); } // Internal object cache for repeat requests - if ( ! empty( $this->custom_statuses_cache ) ) { - return $this->custom_statuses_cache; + if ( ! empty( self::$custom_statuses_cache ) ) { + return self::$custom_statuses_cache; } $statuses = get_terms( [ @@ -763,11 +810,34 @@ public function get_custom_statuses(): array { }, $statuses ); // Set the internal object cache - $this->custom_statuses_cache = $statuses; + self::$custom_statuses_cache = $statuses; return $statuses; } + /** + * Get core's 'draft' and 'pending' post statuses, but include our special attributes + * + * @return array + */ + private static function get_core_post_statuses(): array { + + return array( + (object) array( + 'name' => __( 'Draft' ), + 'description' => '', + 'slug' => 'draft', + 'position' => 1, + ), + (object) array( + 'name' => __( 'Pending Review' ), + 'description' => '', + 'slug' => 'pending', + 'position' => 2, + ), + ); + } + /** * Returns the a single status object based on ID, title, or slug * @@ -775,7 +845,7 @@ public function get_custom_statuses(): array { * @param int|string $value The value to search for * @return WP_Term|false $status The object for the matching status */ - public function get_custom_status_by( string $field, int|string $value ): WP_Term|false { + public static function get_custom_status_by( string $field, int|string $value ): WP_Term|false { // We only support id, slug and name for lookup. if ( ! in_array( $field, [ 'id', 'slug', 'name' ] ) ) { return false; @@ -806,7 +876,7 @@ public function get_custom_status_by( string $field, int|string $value ): WP_Ter * @param string $new_status Slug for the new status * @return true|WP_Error */ - public function reassign_post_status( string $old_status, string $new_status ): bool|WP_Error { + public static function reassign_post_status( string $old_status, string $new_status ): bool|WP_Error { $old_status_post_ids = ( new WP_Query( [ 'post_type' => VIP_Workflow::instance()->get_supported_post_types(), 'post_status' => $old_status, @@ -857,7 +927,7 @@ public function reassign_post_status( string $old_status, string $new_status ): * * @return array $post_states */ - public function add_status_to_post_states( array $post_states, WP_Post $post ) { + public static function add_status_to_post_states( array $post_states, WP_Post $post ): array { if ( ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types(), true ) ) { // Return early if this post type doesn't support custom statuses. return $post_states; @@ -889,7 +959,7 @@ public function add_status_to_post_states( array $post_states, WP_Post $post ) { * @param string $slug Slug of the status * @return bool $restricted True if restricted, false if not */ - public function is_restricted_status( $slug ) { + public static function is_restricted_status( string $slug ): bool { switch ( $slug ) { case 'publish': @@ -909,51 +979,13 @@ public function is_restricted_status( $slug ) { return $restricted; } - /** - * Primary configuration page for custom status class, which is also the main entry point for configuring the plugin - */ - public function render_settings_view() { - include_once __DIR__ . '/views/manage-workflow.php'; - } - - /** - * Given a post ID, return true if the extended post status allows for publishing. - * - * @param int $post_id The post ID being queried. - * @return bool True if the post should not be published based on the extended post status, false otherwise. - */ - public function workflow_is_publish_blocked( $post_id ) { - $post = get_post( $post_id ); - - if ( null === $post ) { - return false; - } - - $custom_statuses = $this->get_custom_statuses(); - $status_slugs = wp_list_pluck( $custom_statuses, 'slug' ); - - if ( ! in_array( $post->post_status, $status_slugs ) || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { - // Post is not using a custom status, or is not a supported post type - return false; - } - - $status_before_publish = $custom_statuses[ array_key_last( $custom_statuses ) ]; - - if ( $status_before_publish->slug === $post->post_status ) { - // Post is in the last status, so it can be published - return true; - } else { - return false; - } - } - /** * Given a post ID, return true if the post type is supported and using a custom status, false otherwise. * * @param int $post_id The post ID being queried. * @return bool True if the post is using a custom status, false otherwise. */ - public function is_post_using_custom_status( $post_id ) { + public static function is_post_using_custom_status( int $post_id ): bool { $post = get_post( $post_id ); if ( null === $post ) { @@ -961,7 +993,7 @@ public function is_post_using_custom_status( $post_id ) { } $custom_post_types = VIP_Workflow::instance()->get_supported_post_types(); - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); $status_slugs = wp_list_pluck( $custom_statuses, 'slug' ); return in_array( $post->post_type, $custom_post_types ) && in_array( $post->post_status, $status_slugs ); @@ -976,9 +1008,13 @@ public function is_post_using_custom_status( $post_id ) { * * @see https://github.com/Automattic/Edit-Flow/issues/523 * @see https://github.com/Automattic/Edit-Flow/issues/633 + * + * @param array $data The post data + * @param array $postarr The post array + * @return array $data The post data */ - public function maybe_keep_post_name_empty( $data, $postarr ) { - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + public static function maybe_keep_post_name_empty( array $data, array $postarr ): array { + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); // Ignore if it's not a post status and post type we support if ( ! in_array( $data['post_status'], $status_slugs ) @@ -1005,16 +1041,24 @@ public function maybe_keep_post_name_empty( $data, $postarr ) { * really hard to set `post_name`, and we leverage `wp_unique_post_slug` to prevent it being set * * @see: https://github.com/WordPress/WordPress/blob/396647666faebb109d9cd4aada7bb0c7d0fb8aca/wp-includes/post.php#L3932 + * + * @param string|null $override_slug The override slug + * @param string $slug The slug + * @param int $post_id The post ID + * @param string $post_status The post status + * @param string $post_type The post type + * @param int $post_parent The post parent + * @return string|null $override_slug The override slug */ - public function fix_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type, $post_parent ) { - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + public static function fix_unique_post_slug( string|null $override_slug, string $slug, int $post_id, string $post_status, string $post_type, int $post_parent ): string|null { + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! in_array( $post_status, $status_slugs ) || ! in_array( $post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { return null; } - $post = get_post( $post_ID ); + $post = get_post( $post_id ); if ( empty( $post ) ) { return null; @@ -1032,14 +1076,17 @@ public function fix_unique_post_slug( $override_slug, $slug, $post_ID, $post_sta * Another hack! hack! hack! until core better supports custom statuses * * The preview link for an unpublished post should always be ?p= + * + * @param string $preview_link The preview link + * @return string $preview_link The preview link */ - public function fix_preview_link_part_one( $preview_link ) { + public static function fix_preview_link_part_one( string $preview_link ): string { global $pagenow; $post = get_post( get_the_ID() ); // Only modify if we're using a pre-publish status on a supported custom post type - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! $post || ! is_admin() || 'post.php' != $pagenow @@ -1050,7 +1097,7 @@ public function fix_preview_link_part_one( $preview_link ) { return $preview_link; } - return $this->get_preview_link( $post ); + return self::get_preview_link( $post ); } /** @@ -1060,8 +1107,13 @@ public function fix_preview_link_part_one( $preview_link ) { * The code used to trigger a post preview doesn't also apply the 'preview_post_link' filter * So we can't do a targeted filter. Instead, we can even more hackily filter get_permalink * @see http://core.trac.wordpress.org/ticket/19378 + * + * @param string $permalink The permalink + * @param int|WP_Post $post The post object + * @param bool $sample Is this a sample permalink? + * @return string $permalink The permalink */ - public function fix_preview_link_part_two( $permalink, $post, $sample ) { + public static function fix_preview_link_part_two( string $permalink, int|WP_Post $post, bool $sample ): string { global $pagenow; if ( is_int( $post ) ) { @@ -1074,7 +1126,7 @@ public function fix_preview_link_part_two( $permalink, $post, $sample ) { } //Is this published? - if ( in_array( $post->post_status, $this->published_statuses ) ) { + if ( in_array( $post->post_status, self::$published_statuses ) ) { return $permalink; } @@ -1096,7 +1148,7 @@ public function fix_preview_link_part_two( $permalink, $post, $sample ) { return $permalink; } - return $this->get_preview_link( $post ); + return self::get_preview_link( $post ); } /** @@ -1105,8 +1157,12 @@ public function fix_preview_link_part_two( $permalink, $post, $sample ) { * The preview link for a saved unpublished post with a custom status returns a 'preview_nonce' * in it and needs to be removed when previewing it to return a viewable preview link. * @see https://github.com/Automattic/Edit-Flow/issues/513 + * + * @param string $preview_link The preview link + * @param WP_Post $query_args The post object + * @return string $preview_link The preview link */ - public function fix_preview_link_part_three( $preview_link, $query_args ) { + public static function fix_preview_link_part_three( string $preview_link, WP_Post $query_args ) { $autosave = wp_get_post_autosave( $query_args->ID, get_current_user_id() ); if ( $autosave ) { foreach ( array_intersect( array_keys( _wp_post_revision_fields( $query_args ) ), array_keys( _wp_post_revision_fields( $autosave ) ) ) as $field ) { @@ -1126,23 +1182,23 @@ public function fix_preview_link_part_three( $preview_link, $query_args ) { * manipulating the slug. Critical for cases like editing the sample permalink on * hierarchical post types. * - * @param string $permalink Sample permalink + * @param array $permalink Sample permalink * @param int $post_id Post ID * @param string $title Post title * @param string $name Post name (slug) * @param WP_Post $post Post object - * @return string $link Direct link to complete the action + * @return array $link Direct link to complete the action */ - public function fix_get_sample_permalink( $permalink, $post_id, $title, $name, $post ) { + public static function fix_get_sample_permalink( array $permalink, int $post_id, string|null $title, string|null $name, WP_Post $post ): array { - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! in_array( $post->post_status, $status_slugs ) || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { return $permalink; } - remove_filter( 'get_sample_permalink', [ $this, 'fix_get_sample_permalink' ], 10, 5 ); + remove_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); $new_name = ! is_null( $name ) ? $name : $post->post_name; $new_title = ! is_null( $title ) ? $title : $post->post_title; @@ -1155,7 +1211,7 @@ public function fix_get_sample_permalink( $permalink, $post_id, $title, $name, $ $post->post_status = $status_before; - add_filter( 'get_sample_permalink', [ $this, 'fix_get_sample_permalink' ], 10, 5 ); + add_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); return $permalink; } @@ -1170,26 +1226,27 @@ public function fix_get_sample_permalink( $permalink, $post_id, $title, $name, $ * to support this link * @see https://core.trac.wordpress.org/browser/tags/4.5.2/src/wp-admin/includes/post.php#L1296 * - * @param string $return Sample permalink HTML markup. + * @param array $return Sample permalink HTML markup. * @param int $post_id Post ID. * @param string $new_title New sample permalink title. * @param string $new_slug New sample permalink slug. * @param WP_Post $post Post object. + * @return array $sample_permalink_html */ - public function fix_get_sample_permalink_html( $permalink, $post_id, $new_title, $new_slug, $post ) { - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + public static function fix_get_sample_permalink_html( array $permalink, int $post_id, string|null $title, string|null $name, WP_Post $post ): array { + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! in_array( $post->post_status, $status_slugs ) || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { return $permalink; } - remove_filter( 'get_sample_permalink_html', [ $this, 'fix_get_sample_permalink_html' ], 10, 5 ); + remove_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); $post->post_status = 'draft'; $sample_permalink_html = get_sample_permalink_html( $post, $new_title, $new_slug ); - add_filter( 'get_sample_permalink_html', [ $this, 'fix_get_sample_permalink_html' ], 10, 5 ); + add_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); return $sample_permalink_html; } @@ -1203,12 +1260,12 @@ public function fix_get_sample_permalink_html( $permalink, $post_id, $new_title, * * Used by `wp_link_pages_link` filter * - * @param $link - * @param $i + * @param string $link The link + * @param string $i The page number * - * @return string + * @return string $link The modified link */ - public function modify_preview_link_pagination_url( $link, $i ) { + public static function modify_preview_link_pagination_url( string $link, string $i ) { // Use the original $link when not in preview mode if ( ! is_preview() ) { @@ -1216,7 +1273,7 @@ public function modify_preview_link_pagination_url( $link, $i ) { } // Get an array of valid custom status slugs - $custom_statuses = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + $custom_statuses = wp_list_pluck( self::get_custom_statuses(), 'slug' ); // Apply original link filters from core `wp_link_pages()` $r = apply_filters( 'wp_link_pages_args', [ @@ -1236,8 +1293,11 @@ public function modify_preview_link_pagination_url( $link, $i ) { /** * Get the proper preview link for a post + * + * @param WP_Post $post The post object + * @return string $preview_link The preview link */ - private function get_preview_link( $post ) { + private static function get_preview_link( WP_Post $post ): string { if ( 'page' === $post->post_type ) { $args = [ @@ -1265,11 +1325,11 @@ private function get_preview_link( $post ) { * The preview link for an unpublished post should always be ?p=, even in the list table * @see http://core.trac.wordpress.org/ticket/19378 */ - public function fix_post_row_actions( $actions, $post ) { + public static function fix_post_row_actions( array $actions, WP_Post $post ): array { global $pagenow; // Only modify if we're using a pre-publish status on a supported custom post type - $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' ); + $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( 'edit.php' != $pagenow || ! in_array( $post->post_status, $status_slugs ) || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { @@ -1304,3 +1364,4 @@ public function fix_post_row_actions( $actions, $post ) { } } +CustomStatus::init(); diff --git a/modules/custom-status/meta/position-handler.php b/modules/custom-status/meta/position-handler.php index 48722476..c4fcdb3a 100644 --- a/modules/custom-status/meta/position-handler.php +++ b/modules/custom-status/meta/position-handler.php @@ -7,7 +7,7 @@ namespace VIPWorkflow\Modules\CustomStatus\Meta; -use VIPWorkflow\Modules\Custom_Status; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Shared\PHP\MetaCleanupUtilities; use WP_Term; @@ -29,9 +29,9 @@ public static function init(): void { * @return array The updated meta keys */ public static function add_position( array $term_meta, WP_Term $custom_status ): array { - $position = MetaCleanupUtilities::get_int( $custom_status->term_id, Custom_Status::METADATA_POSITION_KEY ); + $position = MetaCleanupUtilities::get_int( $custom_status->term_id, CustomStatus::METADATA_POSITION_KEY ); - $term_meta[ Custom_Status::METADATA_POSITION_KEY ] = $position; + $term_meta[ CustomStatus::METADATA_POSITION_KEY ] = $position; return $term_meta; } @@ -43,7 +43,7 @@ public static function add_position( array $term_meta, WP_Term $custom_status ): * @return void */ public static function delete_position( int $term_id ): void { - delete_term_meta( $term_id, Custom_Status::METADATA_POSITION_KEY ); + delete_term_meta( $term_id, CustomStatus::METADATA_POSITION_KEY ); } } diff --git a/modules/custom-status/meta/required-metadata-id-handler.php b/modules/custom-status/meta/required-metadata-id-handler.php index add8fe84..1f18e194 100644 --- a/modules/custom-status/meta/required-metadata-id-handler.php +++ b/modules/custom-status/meta/required-metadata-id-handler.php @@ -7,8 +7,7 @@ namespace VIPWorkflow\Modules\CustomStatus\Meta; -use VIPWorkflow\Modules\Custom_Status; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Shared\PHP\MetaCleanupUtilities; use WP_Term; @@ -34,9 +33,9 @@ public static function init(): void { * @return array The updated meta keys */ public static function add_required_metadata_ids( array $term_meta, WP_Term $custom_status ): array { - $metadata_ids = MetaCleanupUtilities::get_array( $custom_status->term_id, Custom_Status::METADATA_REQ_EDITORIAL_IDS_KEY ); + $metadata_ids = MetaCleanupUtilities::get_array( $custom_status->term_id, CustomStatus::METADATA_REQ_EDITORIAL_IDS_KEY ); - $term_meta[ Custom_Status::METADATA_REQ_EDITORIAL_IDS_KEY ] = $metadata_ids; + $term_meta[ CustomStatus::METADATA_REQ_EDITORIAL_IDS_KEY ] = $metadata_ids; return $term_meta; } @@ -48,7 +47,7 @@ public static function add_required_metadata_ids( array $term_meta, WP_Term $cus * @return void */ public static function delete_required_metadata( int $term_id ): void { - delete_term_meta( $term_id, Custom_Status::METADATA_REQ_EDITORIAL_IDS_KEY ); + delete_term_meta( $term_id, CustomStatus::METADATA_REQ_EDITORIAL_IDS_KEY ); } /** @@ -58,9 +57,9 @@ public static function delete_required_metadata( int $term_id ): void { * @return void */ public static function remove_deleted_metadata_from_required_metadata( int $deleted_meta_id ): void { - $custom_statuses = VIP_Workflow::instance()->custom_status->get_custom_statuses(); + $custom_statuses = CustomStatus::get_custom_statuses(); - MetaCleanupUtilities::cleanup_id( $custom_statuses, $deleted_meta_id, /* id_to_replace */ null, Custom_Status::METADATA_REQ_EDITORIAL_IDS_KEY ); + MetaCleanupUtilities::cleanup_id( $custom_statuses, $deleted_meta_id, /* id_to_replace */ null, CustomStatus::METADATA_REQ_EDITORIAL_IDS_KEY ); } } diff --git a/modules/custom-status/meta/required-user-id-handler.php b/modules/custom-status/meta/required-user-id-handler.php index d548123d..f03939f2 100644 --- a/modules/custom-status/meta/required-user-id-handler.php +++ b/modules/custom-status/meta/required-user-id-handler.php @@ -7,8 +7,7 @@ namespace VIPWorkflow\Modules\CustomStatus\Meta; -use VIPWorkflow\Modules\Custom_Status; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Shared\PHP\MetaCleanupUtilities; use WP_Term; @@ -35,12 +34,12 @@ public static function init(): void { * @return array The updated meta keys */ public static function add_required_user_ids( array $term_meta, WP_Term $custom_status ): array { - $user_ids = MetaCleanupUtilities::get_array( $custom_status->term_id, Custom_Status::METADATA_REQ_USER_IDS_KEY ); + $user_ids = MetaCleanupUtilities::get_array( $custom_status->term_id, CustomStatus::METADATA_REQ_USER_IDS_KEY ); - $term_meta[ Custom_Status::METADATA_REQ_USER_IDS_KEY ] = $user_ids; + $term_meta[ CustomStatus::METADATA_REQ_USER_IDS_KEY ] = $user_ids; // For UI purposes, add 'required_users' to the term meta as well. - $term_meta[ Custom_Status::METADATA_REQ_USERS_KEY ] = array_filter( array_map( function ( $user_id ) { + $term_meta[ CustomStatus::METADATA_REQ_USERS_KEY ] = array_filter( array_map( function ( $user_id ) { $user = get_user_by( 'ID', $user_id ); if ( $user instanceof WP_User ) { return [ @@ -50,7 +49,7 @@ public static function add_required_user_ids( array $term_meta, WP_Term $custom_ } else { return false; } - }, $term_meta[ Custom_Status::METADATA_REQ_USER_IDS_KEY ] ) ); + }, $term_meta[ CustomStatus::METADATA_REQ_USER_IDS_KEY ] ) ); return $term_meta; } @@ -62,7 +61,7 @@ public static function add_required_user_ids( array $term_meta, WP_Term $custom_ * @return void */ public static function delete_required_users( int $term_id ): void { - delete_term_meta( $term_id, Custom_Status::METADATA_REQ_USER_IDS_KEY ); + delete_term_meta( $term_id, CustomStatus::METADATA_REQ_USER_IDS_KEY ); } /** @@ -73,9 +72,9 @@ public static function delete_required_users( int $term_id ): void { * @return void */ public static function remove_deleted_user_from_required_users( int $deleted_user_id, int|null $reassigned_user_id ): void { - $custom_statuses = VIP_Workflow::instance()->custom_status->get_custom_statuses(); + $custom_statuses = CustomStatus::get_custom_statuses(); - MetaCleanupUtilities::cleanup_id( $custom_statuses, $deleted_user_id, $reassigned_user_id, Custom_Status::METADATA_REQ_USER_IDS_KEY ); + MetaCleanupUtilities::cleanup_id( $custom_statuses, $deleted_user_id, $reassigned_user_id, CustomStatus::METADATA_REQ_USER_IDS_KEY ); } } diff --git a/modules/custom-status/rest/custom-status-endpoint.php b/modules/custom-status/rest/custom-status-endpoint.php index 0dbeda7c..688dd5be 100644 --- a/modules/custom-status/rest/custom-status-endpoint.php +++ b/modules/custom-status/rest/custom-status-endpoint.php @@ -6,9 +6,8 @@ namespace VIPWorkflow\Modules\CustomStatus\REST; -use VIPWorkflow\Modules\Custom_Status; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\EditorialMetadata; -use VIPWorkflow\VIP_Workflow; use WP_Error; use WP_REST_Request; use WP_Term; @@ -92,7 +91,7 @@ public static function register_routes() { 'required' => true, 'validate_callback' => function ( $param ) { $term_id = absint( $param ); - $term = get_term( $term_id, Custom_Status::TAXONOMY_KEY ); + $term = get_term( $term_id, CustomStatus::TAXONOMY_KEY ); return ( $term instanceof WP_Term ); }, 'sanitize_callback' => function ( $param ) { @@ -140,7 +139,7 @@ public static function register_routes() { 'required' => true, 'validate_callback' => function ( $param ) { $term_id = absint( $param ); - $term = get_term( $term_id, Custom_Status::TAXONOMY_KEY ); + $term = get_term( $term_id, CustomStatus::TAXONOMY_KEY ); return ( $term instanceof WP_Term ); }, 'sanitize_callback' => function ( $param ) { @@ -166,7 +165,7 @@ public static function register_routes() { // validate each item in the array. foreach ( $param as $position => $term_id ) { $term_id = absint( $term_id ); - $term = get_term( $term_id, Custom_Status::TAXONOMY_KEY ); + $term = get_term( $term_id, CustomStatus::TAXONOMY_KEY ); if ( ! $term instanceof WP_Term ) { return false; } @@ -205,15 +204,13 @@ public static function handle_create_status( WP_REST_Request $request ) { $status_required_metadata_ids = $request->get_param( 'required_metadata_ids' ); $status_required_user_ids = $request->get_param( 'required_user_ids' ); - $custom_status_module = VIP_Workflow::instance()->custom_status; - // Check that the name isn't numeric if ( is_numeric( $status_name ) ) { return new WP_Error( 'invalid', 'Please enter a valid, non-numeric name for the status.' ); } // Check to make sure the name is not restricted - if ( $custom_status_module->is_restricted_status( strtolower( $status_name ) ) ) { + if ( CustomStatus::is_restricted_status( strtolower( $status_name ) ) ) { return new WP_Error( 'invalid', 'Status name is restricted. Please chose another name.' ); } @@ -223,7 +220,7 @@ public static function handle_create_status( WP_REST_Request $request ) { } // Check to make sure the status doesn't already exist as another term because otherwise we'd get a fatal error - $term_exists = term_exists( $status_slug, Custom_Status::TAXONOMY_KEY ); + $term_exists = term_exists( $status_slug, CustomStatus::TAXONOMY_KEY ); if ( $term_exists ) { return new WP_Error( 'invalid', 'Status name conflicts with existing term. Please choose another.' ); @@ -237,7 +234,7 @@ public static function handle_create_status( WP_REST_Request $request ) { 'required_user_ids' => $status_required_user_ids, ]; - $add_status_result = $custom_status_module->add_custom_status( $args ); + $add_status_result = CustomStatus::add_custom_status( $args ); // Regardless of an error being thrown, the result will be returned so the client can handle it. return rest_ensure_response( $add_status_result ); @@ -256,15 +253,13 @@ public static function handle_update_status( WP_REST_Request $request ) { $status_required_metadata_ids = $request->get_param( 'required_metadata_ids' ); $status_required_user_ids = $request->get_param( 'required_user_ids' ); - $custom_status_module = VIP_Workflow::instance()->custom_status; - // Check that the name isn't numeric if ( is_numeric( $status_name ) ) { return new WP_Error( 'invalid', 'Please enter a valid, non-numeric name for the status.' ); } // Check to make sure the name is not restricted - if ( $custom_status_module->is_restricted_status( strtolower( $status_name ) ) ) { + if ( CustomStatus::is_restricted_status( strtolower( $status_name ) ) ) { return new WP_Error( 'invalid', 'Status name is restricted. Please chose another name.' ); } @@ -274,15 +269,15 @@ public static function handle_update_status( WP_REST_Request $request ) { } // Check to make sure the status doesn't already exist - $custom_status_by_id = $custom_status_module->get_custom_status_by( 'id', $term_id ); - $custom_status_by_slug = $custom_status_module->get_custom_status_by( 'slug', $status_slug ); + $custom_status_by_id = CustomStatus::get_custom_status_by( 'id', $term_id ); + $custom_status_by_slug = CustomStatus::get_custom_status_by( 'slug', $status_slug ); if ( $custom_status_by_slug && $custom_status_by_id && $custom_status_by_id->slug !== $status_slug ) { return new WP_Error( 'invalid', 'Status already exists. Please choose another name.' ); } // Check to make sure the status doesn't already exist as another term because otherwise we'd get a fatal error - $term_exists = term_exists( $status_slug, Custom_Status::TAXONOMY_KEY ); + $term_exists = term_exists( $status_slug, CustomStatus::TAXONOMY_KEY ); // term_id from term_exists is a string, while term_id is an integer so not using strict comparison if ( $term_exists && isset( $term_exists['term_id'] ) && $term_exists['term_id'] != $term_id ) { @@ -298,7 +293,7 @@ public static function handle_update_status( WP_REST_Request $request ) { 'required_user_ids' => $status_required_user_ids, ]; - $updated_status = $custom_status_module->update_custom_status( $term_id, $args ); + $updated_status = CustomStatus::update_custom_status( $term_id, $args ); // Regardless of an error being thrown, the result will be returned so the client can handle it. return rest_ensure_response( $updated_status ); @@ -312,15 +307,13 @@ public static function handle_update_status( WP_REST_Request $request ) { public static function handle_delete_status( WP_REST_Request $request ) { $term_id = $request->get_param( 'id' ); - $custom_status_module = VIP_Workflow::instance()->custom_status; - // Check to make sure the status exists - $custom_status_by_id = $custom_status_module->get_custom_status_by( 'id', $term_id ); + $custom_status_by_id = CustomStatus::get_custom_status_by( 'id', $term_id ); if ( ! $custom_status_by_id ) { return new WP_Error( 'invalid', 'Status does not exist.' ); } - $delete_status_result = $custom_status_module->delete_custom_status( $term_id ); + $delete_status_result = CustomStatus::delete_custom_status( $term_id ); // Regardless of an error being thrown, the result will be returned so the client can handle it. return rest_ensure_response( $delete_status_result ); @@ -334,8 +327,6 @@ public static function handle_delete_status( WP_REST_Request $request ) { public static function handle_reorder_status( WP_REST_Request $request ) { $status_order = $request->get_param( 'status_positions' ); - $custom_status_module = VIP_Workflow::instance()->custom_status; - if ( ! is_array( $status_order ) ) { return new WP_Error( 'invalid', 'Status order must be an array.' ); } @@ -347,7 +338,7 @@ public static function handle_reorder_status( WP_REST_Request $request ) { 'position' => absint( $position ) + 1, ]; - $update_status_result = $custom_status_module->update_custom_status( (int) $term_id, $args ); + $update_status_result = CustomStatus::update_custom_status( (int) $term_id, $args ); // Stop the operation immediately if something has gone wrong, rather than silently continuing. if ( is_wp_error( $update_status_result ) ) { @@ -356,7 +347,7 @@ public static function handle_reorder_status( WP_REST_Request $request ) { } // Regardless of an error being thrown, the result will be returned so the client can handle it. - return rest_ensure_response( $custom_status_module->get_custom_statuses() ); + return rest_ensure_response( CustomStatus::get_custom_statuses() ); } // Utility functions diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index 65226217..b9050a8c 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -142,7 +142,7 @@ public static function setup_install(): void { public static function add_admin_menu(): void { $menu_title = __( 'Editorial Metadata', 'vip-workflow' ); - add_submenu_page( Custom_Status::SETTINGS_SLUG, $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ __CLASS__, 'render_settings_view' ] ); + add_submenu_page( CustomStatus::SETTINGS_SLUG, $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ __CLASS__, 'render_settings_view' ] ); } /** diff --git a/modules/preview/preview.php b/modules/preview/preview.php index 4a922ac9..6e6262fa 100644 --- a/modules/preview/preview.php +++ b/modules/preview/preview.php @@ -10,6 +10,7 @@ require_once __DIR__ . '/token.php'; require_once __DIR__ . '/rest/preview-endpoint.php'; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Preview\PreviewEndpoint; use VIPWorkflow\VIP_Workflow; use VIPWorkflow\Modules\Preview\Token; @@ -43,9 +44,7 @@ public static function load_block_editor_scripts(): void { if ( $post_id ) { $generate_preview_url = PreviewEndpoint::get_url( $post_id ); } - - $custom_status_module = VIP_Workflow::instance()->custom_status; - $custom_status_slugs = wp_list_pluck( $custom_status_module->get_custom_statuses(), 'slug' ); + $custom_status_slugs = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); $custom_post_types = VIP_Workflow::instance()->get_supported_post_types(); wp_localize_script( 'vip-workflow-preview-script', 'VW_PREVIEW', [ diff --git a/modules/preview/rest/preview-endpoint.php b/modules/preview/rest/preview-endpoint.php index f938d463..0326ead0 100644 --- a/modules/preview/rest/preview-endpoint.php +++ b/modules/preview/rest/preview-endpoint.php @@ -7,7 +7,7 @@ namespace VIPWorkflow\Modules\Preview; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Preview; use WP_Error; use WP_REST_Request; @@ -88,7 +88,7 @@ public static function handle_generate_preview_token( WP_REST_Request $request ) $expiration_value = $request->get_param( 'expiration' ); $is_one_time_use = $request->get_param( 'is_one_time_use' ); - if ( ! VIP_Workflow::instance()->custom_status->is_post_using_custom_status( $post_id ) ) { + if ( ! CustomStatus::is_post_using_custom_status( $post_id ) ) { $post_status = get_post_status( $post_id ); if ( 'publish' === $post_status ) { diff --git a/modules/preview/token.php b/modules/preview/token.php index 73023199..45096666 100644 --- a/modules/preview/token.php +++ b/modules/preview/token.php @@ -6,7 +6,7 @@ */ namespace VIPWorkflow\Modules\Preview; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; use WP_Error; class Token { @@ -55,7 +55,7 @@ public static function generate_token( $post_id, $is_one_time_use, $expiration_s */ public static function validate_token( $token, $post_id ) { $saved_tokens = get_post_meta( $post_id, self::META_KEY, /* single */ false ); - $is_post_using_custom_status = VIP_Workflow::instance()->custom_status->is_post_using_custom_status( $post_id ); + $is_post_using_custom_status = CustomStatus::is_post_using_custom_status( $post_id ); if ( count( $saved_tokens ) > 0 && ! $is_post_using_custom_status ) { // Clear all tokens if this post is no longer using a custom status and return false. diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 95d49287..d7cd7d06 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -58,7 +58,7 @@ public function init() { public function add_admin_menu() { $menu_title = __( 'Settings', 'vip-workflow' ); - add_submenu_page( Custom_Status::SETTINGS_SLUG, $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ $this, 'render_settings_view' ] ); + add_submenu_page( CustomStatus::SETTINGS_SLUG, $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ $this, 'render_settings_view' ] ); } /** diff --git a/modules/shared/php/class-module.php b/modules/shared/php/class-module.php index 30f42f02..4637aa61 100644 --- a/modules/shared/php/class-module.php +++ b/modules/shared/php/class-module.php @@ -11,36 +11,12 @@ class Module { - public $published_statuses = array( - 'publish', - 'future', - 'private', - ); - public $module_url; public $module; public function __construct() {} - /** - * Check if the site is a WPVIP site. - * - * @param bool $only_production Whether to only allow production sites to be considered WPVIP sites - * @return true, if it is a WPVIP site, false otherwise - */ - protected function is_vip_site( $only_production = false ) { - $is_vip_site = defined( 'VIP_GO_ENV' ) - && defined( 'WPCOM_SANDBOXED' ) && constant( 'WPCOM_SANDBOXED' ) === false - && defined( 'FILES_CLIENT_SITE_ID' ); - - if ( $only_production ) { - $is_vip_site = $is_vip_site && defined( 'VIP_GO_ENV' ) && 'production' === constant( 'VIP_GO_ENV' ); - } - - return $is_vip_site; - } - /** * Gets an array of allowed post types for a module * @@ -75,92 +51,6 @@ public function clean_post_type_options( $module_post_types = array() ) { return $normalized_post_type_options; } - /** - * Get core's 'draft' and 'pending' post statuses, but include our special attributes - * - * @return array - */ - protected function get_core_post_statuses() { - - return array( - (object) array( - 'name' => __( 'Draft' ), - 'description' => '', - 'slug' => 'draft', - 'position' => 1, - ), - (object) array( - 'name' => __( 'Pending Review' ), - 'description' => '', - 'slug' => 'pending', - 'position' => 2, - ), - ); - } - - /** - * Filter to all posts with a given post status (can be a custom status or a built-in status) and optional custom post type. - * - * @param string $slug The slug for the post status to which to filter - * @param string $post_type Optional post type to which to filter - * @return an edit.php link to all posts with the given post status and, optionally, the given post type - */ - public function filter_posts_link( $slug, $post_type = 'post' ) { - $filter_link = add_query_arg( 'post_status', $slug, get_admin_url( null, 'edit.php' ) ); - if ( 'post' != $post_type && in_array( $post_type, get_post_types( '', 'names' ) ) ) { - $filter_link = add_query_arg( 'post_type', $post_type, $filter_link ); - } - return $filter_link; - } - - /** - * Checks for the current post type - * - * @return string|null $post_type The post type we've found, or null if no post type - */ - public function get_current_post_type() { - global $post, $typenow, $pagenow, $current_screen; - //get_post() needs a variable - $post_id = isset( $_REQUEST['post'] ) ? (int) $_REQUEST['post'] : false; - - if ( $post && $post->post_type ) { - $post_type = $post->post_type; - } elseif ( $typenow ) { - $post_type = $typenow; - } elseif ( $current_screen && ! empty( $current_screen->post_type ) ) { - $post_type = $current_screen->post_type; - } elseif ( isset( $_REQUEST['post_type'] ) ) { - $post_type = sanitize_key( $_REQUEST['post_type'] ); - } elseif ( 'post.php' == $pagenow - && $post_id - && ! empty( get_post( $post_id )->post_type ) ) { - $post_type = get_post( $post_id )->post_type; - } elseif ( 'edit.php' == $pagenow && empty( $_REQUEST['post_type'] ) ) { - $post_type = 'post'; - } else { - $post_type = null; - } - - return $post_type; - } - - /** - * Take a status and a message, JSON encode and print - * - * @param string $status Whether it was a 'success' or an 'error' - */ - protected function print_ajax_response( $status, $message = '', $http_code = 200 ) { - header( 'Content-type: application/json;' ); - http_response_code( $http_code ); - echo wp_json_encode( - array( - 'status' => $status, - 'message' => $message, - ) - ); - exit; - } - /** * Get the publicly accessible URL for the module based on the filename * diff --git a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php index ff56127e..611e8170 100644 --- a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php @@ -7,7 +7,7 @@ */ namespace VIPWorkflow\Tests; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\CustomStatus\Meta\RequiredMetadataIdHandler; use WP_UnitTestCase; @@ -22,12 +22,12 @@ protected function setUp(): void { // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes // a status and it's the only status existing, which can cause errors due to status restrictions. - VIP_Workflow::instance()->custom_status->install(); + CustomStatus::setup_install(); } public function test_remove_deleted_metadata_from_required_metadata() { $meta_id = 1; - $custom_status_term = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $custom_status_term = CustomStatus::add_custom_status( [ 'name' => 'Test Custom Status', 'slug' => 'test-custom-status', 'description' => 'Test Description.', @@ -37,12 +37,12 @@ public function test_remove_deleted_metadata_from_required_metadata() { RequiredMetadataIdHandler::remove_deleted_metadata_from_required_metadata( $meta_id ); - $updated_term = VIP_Workflow::instance()->custom_status->get_custom_status_by( 'id', $term_id ); + $updated_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'Test Custom Status', $updated_term->name ); $this->assertEquals( 'Test Description.', $updated_term->description ); $this->assertEmpty( $updated_term->meta['required_metadata_ids'] ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term_id ); + CustomStatus::delete_custom_status( $term_id ); } } diff --git a/tests/modules/custom-status/meta/test-required-user-id-handler.php b/tests/modules/custom-status/meta/test-required-user-id-handler.php index 942eb09f..705cca1c 100644 --- a/tests/modules/custom-status/meta/test-required-user-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-user-id-handler.php @@ -7,7 +7,7 @@ */ namespace VIPWorkflow\Tests; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\CustomStatus\Meta\RequiredUserIdHandler; use WP_UnitTestCase; @@ -22,12 +22,12 @@ protected function setUp(): void { // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes // a status and it's the only status existing, which can cause errors due to status restrictions. - VIP_Workflow::instance()->custom_status->install(); + CustomStatus::setup_install(); } public function test_remove_deleted_user_from_required_users_no_reassigned_user() { $deleted_user_id = 1; - $custom_status_term = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $custom_status_term = CustomStatus::add_custom_status( [ 'name' => 'Test Custom Status', 'slug' => 'test-custom-status', 'description' => 'Test Description.', @@ -37,19 +37,19 @@ public function test_remove_deleted_user_from_required_users_no_reassigned_user( RequiredUserIdHandler::remove_deleted_user_from_required_users( $deleted_user_id, null ); - $updated_term = VIP_Workflow::instance()->custom_status->get_custom_status_by( 'id', $term_id ); + $updated_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'Test Custom Status', $updated_term->name ); $this->assertEquals( 'Test Description.', $updated_term->description ); $this->assertEmpty( $updated_term->meta['required_user_ids'] ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term_id ); + CustomStatus::delete_custom_status( $term_id ); } public function test_remove_deleted_user_from_required_users_with_reassigned_user() { $deleted_user_id = 1; $reassigned_user_id = 2; - $custom_status_term = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $custom_status_term = CustomStatus::add_custom_status( [ 'name' => 'Test Custom Status', 'slug' => 'test-custom-status', 'description' => 'Test Description.', @@ -59,13 +59,13 @@ public function test_remove_deleted_user_from_required_users_with_reassigned_use RequiredUserIdHandler::remove_deleted_user_from_required_users( $deleted_user_id, $reassigned_user_id ); - $updated_term = VIP_Workflow::instance()->custom_status->get_custom_status_by( 'id', $term_id ); + $updated_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'Test Custom Status', $updated_term->name ); $this->assertEquals( 'Test Description.', $updated_term->description ); $this->assertCount( 1, $updated_term->meta['required_user_ids'] ); $this->assertEquals( $reassigned_user_id, $updated_term->meta['required_user_ids'][0] ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term_id ); + CustomStatus::delete_custom_status( $term_id ); } } diff --git a/tests/modules/custom-status/rest/test-custom-status-endpoint.php b/tests/modules/custom-status/rest/test-custom-status-endpoint.php index 5161b03e..6fc40eb8 100644 --- a/tests/modules/custom-status/rest/test-custom-status-endpoint.php +++ b/tests/modules/custom-status/rest/test-custom-status-endpoint.php @@ -7,8 +7,8 @@ namespace VIPWorkflow\Tests; +use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\EditorialMetadata; -use VIPWorkflow\VIP_Workflow; use WP_REST_Request; /** @@ -25,7 +25,7 @@ protected function setUp(): void { // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes // a status and it's the only status existing, which can cause errors due to status restrictions. - VIP_Workflow::instance()->custom_status->install(); + CustomStatus::setup_install(); } public function test_create_custom_status_with_optional_fields() { @@ -56,7 +56,7 @@ public function test_create_custom_status_with_optional_fields() { $this->assertObjectHasProperty( 'term_id', $result, sprintf( 'Unexpected REST output: %s', wp_json_encode( $result ) ) ); $term_id = $result->term_id; - $created_term = VIP_Workflow::instance()->custom_status->get_custom_status_by( 'id', $term_id ); + $created_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'test-status', $created_term->name ); $this->assertEquals( 'A test status for testing', $created_term->description ); @@ -65,7 +65,7 @@ public function test_create_custom_status_with_optional_fields() { $this->assertCount( 1, $created_term->meta['required_metadata_ids'] ); $this->assertEquals( $editorial_metadata_term->term_id, $created_term->meta['required_metadata_ids'][0] ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term_id ); + CustomStatus::delete_custom_status( $term_id ); EditorialMetadata::delete_editorial_metadata_term( $editorial_metadata_term->term_id ); } @@ -87,15 +87,15 @@ public function test_create_custom_status() { $this->assertObjectHasProperty( 'term_id', $result, sprintf( 'Unexpected REST output: %s', wp_json_encode( $result ) ) ); $term_id = $result->term_id; - $created_term = VIP_Workflow::instance()->custom_status->get_custom_status_by( 'id', $term_id ); + $created_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'test-status', $created_term->name ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term_id ); + CustomStatus::delete_custom_status( $term_id ); } public function test_update_custom_status() { - $custom_status_term = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $custom_status_term = CustomStatus::add_custom_status( [ 'name' => 'Test Custom Status', 'slug' => 'test-custom-status', 'description' => 'Test Description.', @@ -119,18 +119,18 @@ public function test_update_custom_status() { $this->assertEquals( 200, $response->get_status() ); - $updated_term = VIP_Workflow::instance()->custom_status->get_custom_status_by( 'id', $term_id ); + $updated_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'Test Custom Status 2', $updated_term->name ); $this->assertEquals( 'Test Description 2!', $updated_term->description ); $this->assertCount( 1, $updated_term->meta['required_user_ids'] ); $this->assertEquals( $editor_user_id, $updated_term->meta['required_user_ids'][0] ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term_id ); + CustomStatus::delete_custom_status( $term_id ); } public function test_delete_custom_status() { - $term_to_delete = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $term_to_delete = CustomStatus::add_custom_status( [ 'name' => 'Custom Status To Delete', 'slug' => 'custom-status-to-delete', ] ); @@ -146,24 +146,24 @@ public function test_delete_custom_status() { $this->assertEquals( 200, $response->get_status() ); - $all_terms = VIP_Workflow::instance()->custom_status->get_custom_statuses(); + $all_terms = CustomStatus::get_custom_statuses(); $all_term_slugs = wp_list_pluck( $all_terms, 'slug' ); $this->assertNotContains( 'custom-status-to-delete', $all_term_slugs ); } public function test_reorder_custom_status() { - $existing_custom_statuses = VIP_Workflow::instance()->custom_status->get_custom_statuses(); + $existing_custom_statuses = CustomStatus::get_custom_statuses(); $existing_custom_status_ids = wp_list_pluck( $existing_custom_statuses, 'term_id' ); - $term1 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $term1 = CustomStatus::add_custom_status( [ 'name' => 'Custom Status 1', 'slug' => 'custom-status-1', ] ); - $term2 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $term2 = CustomStatus::add_custom_status( [ 'name' => 'Custom Status 2', 'slug' => 'custom-status-2', ] ); - $term3 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $term3 = CustomStatus::add_custom_status( [ 'name' => 'Custom Status 3', 'slug' => 'custom-status-3', ] ); @@ -185,13 +185,13 @@ public function test_reorder_custom_status() { $this->assertEquals( 200, $response->get_status() ); - $reordered_custom_statuses = VIP_Workflow::instance()->custom_status->get_custom_statuses(); + $reordered_custom_statuses = CustomStatus::get_custom_statuses(); $this->assertEquals( $term3->term_id, $reordered_custom_statuses[0]->term_id ); $this->assertEquals( $term1->term_id, $reordered_custom_statuses[1]->term_id ); $this->assertEquals( $term2->term_id, $reordered_custom_statuses[2]->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term1->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term2->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $term3->term_id ); + CustomStatus::delete_custom_status( $term1->term_id ); + CustomStatus::delete_custom_status( $term2->term_id ); + CustomStatus::delete_custom_status( $term3->term_id ); } } diff --git a/tests/modules/custom-status/test-custom-status-transitions.php b/tests/modules/custom-status/test-custom-status-transitions.php index 08e3ee43..2b5b6ae1 100644 --- a/tests/modules/custom-status/test-custom-status-transitions.php +++ b/tests/modules/custom-status/test-custom-status-transitions.php @@ -7,7 +7,7 @@ namespace VIPWorkflow\Tests; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\CustomStatus; /** * Ensure restricted posts block updates from unauthorized users. @@ -19,20 +19,20 @@ public function test_transition_restrictions_as_privileged_user() { wp_set_current_user( $admin_user_id ); // Setup statuses, with the second status requiring admin user permissions - $status_1 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $status_1 = CustomStatus::add_custom_status( [ 'name' => 'Status 1', 'position' => -3, 'slug' => 'status-1', ] ); - $status_2_restricted = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $status_2_restricted = CustomStatus::add_custom_status( [ 'name' => 'Status 2 (restricted)', 'position' => -2, 'slug' => 'status-2-restricted', 'required_user_ids' => [ $admin_user_id ], ] ); - $status_3 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $status_3 = CustomStatus::add_custom_status( [ 'name' => 'Status 3', 'position' => -1, 'slug' => 'status-3', @@ -64,9 +64,9 @@ public function test_transition_restrictions_as_privileged_user() { $this->assertEquals( 'status-3', get_post_status( $post_id ) ); // Cleanup - VIP_Workflow::instance()->custom_status->delete_custom_status( $status_1->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $status_2_restricted->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $status_3->term_id ); + CustomStatus::delete_custom_status( $status_1->term_id ); + CustomStatus::delete_custom_status( $status_2_restricted->term_id ); + CustomStatus::delete_custom_status( $status_3->term_id ); wp_set_current_user( null ); wp_delete_user( $admin_user_id ); @@ -77,21 +77,21 @@ public function test_transition_restrictions_as_unprivileged_user() { wp_set_current_user( $author_user_id ); // Setup statuses, with the second status requiring admin user permissions - $status_1 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $status_1 = CustomStatus::add_custom_status( [ 'name' => 'Status 1', 'position' => -3, 'slug' => 'status-1', ] ); $admin_user_id = self::create_user( 'test-admin', [ 'role' => 'administrator' ] ); - $status_2_restricted = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $status_2_restricted = CustomStatus::add_custom_status( [ 'name' => 'Status 2 (restricted)', 'position' => -2, 'slug' => 'status-2-restricted', 'required_user_ids' => [ $admin_user_id ], ] ); - $status_3 = VIP_Workflow::instance()->custom_status->add_custom_status( [ + $status_3 = CustomStatus::add_custom_status( [ 'name' => 'Status 3', 'position' => -1, 'slug' => 'status-3', @@ -123,8 +123,8 @@ public function test_transition_restrictions_as_unprivileged_user() { $this->assertEquals( 'status-2-restricted', get_post_status( $post_id ) ); // Cleanup - VIP_Workflow::instance()->custom_status->delete_custom_status( $status_1->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $status_2_restricted->term_id ); - VIP_Workflow::instance()->custom_status->delete_custom_status( $status_3->term_id ); + CustomStatus::delete_custom_status( $status_1->term_id ); + CustomStatus::delete_custom_status( $status_2_restricted->term_id ); + CustomStatus::delete_custom_status( $status_3->term_id ); } } diff --git a/vip-workflow.php b/vip-workflow.php index 8a3d9e53..dccf773e 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -50,6 +50,7 @@ require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/meta-cleanup-utilities.php'; // Modules +require_once VIP_WORKFLOW_ROOT . '/modules/custom-status/custom-status.php'; require_once VIP_WORKFLOW_ROOT . '/modules/editorial-metadata/editorial-metadata.php'; require_once VIP_WORKFLOW_ROOT . '/modules/notifications/notifications.php'; require_once VIP_WORKFLOW_ROOT . '/modules/preview/preview.php'; From fcc0e6bdb97d3064aa238cdfb2e2e5ae607024ad Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 4 Oct 2024 17:11:10 +1000 Subject: [PATCH 02/21] migrate the workflow class --- class-workflow.php | 257 +---------------------- modules/custom-status/custom-status.php | 5 +- modules/settings/settings.php | 178 ++++++++-------- modules/shared/php/options-utilities.php | 39 +++- vip-workflow.php | 2 + webpack.config.js | 2 +- 6 files changed, 141 insertions(+), 342 deletions(-) diff --git a/class-workflow.php b/class-workflow.php index 449565c3..55b58d0a 100644 --- a/class-workflow.php +++ b/class-workflow.php @@ -4,6 +4,7 @@ use VIPWorkflow\Modules\Shared\PHP\Module; use stdClass; +use VIPWorkflow\Modules\Settings; // Core class #[\AllowDynamicProperties] @@ -13,136 +14,18 @@ class VIP_Workflow { public $options_group = 'vip_workflow_'; public $options_group_name = 'vip_workflow_options'; - /** - * @var VIP_Workflow The one true VIP_Workflow - */ - private static $instance; - - /** - * Active modules. - * - * @var \stdClass - */ - public $modules; - - /** - * Number of active modules. - * - * @var int - */ - public $modules_count; - - /** - * @var WPVIPW_Module - */ - public $helpers; - - /** - * Main VIP Workflow Instance - * - * Insures that only one instance of VIP Workflow exists in memory at any one - * time. Also prevents needing to define globals all over the place. - * - * @staticvar array $instance - * @uses VIP_Workflow::setup_globals() Setup the globals needed - * @uses VIP_Workflow::includes() Include the required files - * @uses VIP_Workflow::setup_actions() Setup the hooks and actions - * @see VIP_Workflow() - * @return The one true VIP_Workflow - */ - public static function instance() { - if ( ! isset( self::$instance ) ) { - self::$instance = new VIP_Workflow(); - self::$instance->setup_globals(); - self::$instance->setup_actions(); - } - return self::$instance; - } - - private function __construct() { - /** Do nothing **/ - } - - private function setup_globals() { - $this->modules = new stdClass(); - $this->modules_count = 0; - } - - /** - * Include the common resources to VIP Workflow and dynamically load the modules - */ - private function load_modules() { - // VIP Workflow base module - require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/class-module.php'; - - $skip_module_dirs = [ 'shared', 'preview', 'notifications', 'editorial-metadata', 'custom-status' ]; - - // Scan the modules directory and include any modules that exist there - $module_dirs = scandir( VIP_WORKFLOW_ROOT . '/modules/' ); - $class_names = []; - foreach ( $module_dirs as $module_dir ) { - // Skip the . and .. directories, as well as the shared folder - if ( file_exists( VIP_WORKFLOW_ROOT . "/modules/{$module_dir}/$module_dir.php" ) && ! in_array( $module_dir, $skip_module_dirs ) ) { - include_once VIP_WORKFLOW_ROOT . "/modules/{$module_dir}/$module_dir.php"; - - // Prepare the class name because it should be standardized - $tmp = explode( '-', $module_dir ); - $class_name = ''; - $slug_name = ''; - foreach ( $tmp as $word ) { - $class_name .= ucfirst( $word ) . '_'; - $slug_name .= $word . '_'; - } - $slug_name = rtrim( $slug_name, '_' ); - $class_names[ $slug_name ] = 'VIPWorkflow\Modules\\' . rtrim( $class_name, '_' ); - } - } - - // Instantiate VW_Module as $helpers for back compat and so we can - // use it in this class - $this->helpers = new Module(); - - // Other utils - require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/util.php'; - - // Instantiate all of our classes onto the VIP Workflow object - // but make sure they exist too - foreach ( $class_names as $slug => $class_name ) { - if ( class_exists( $class_name ) ) { - $this->$slug = new $class_name(); - } - } - } - /** * Setup the default hooks and actions * * @uses add_action() To add various actions */ - private function setup_actions() { + private function init() { add_action( 'init', [ $this, 'action_init' ], 8 ); add_action( 'init', [ $this, 'action_init_after' ], 1000 ); add_action( 'admin_init', [ $this, 'action_admin_init' ] ); } - /** - * Inititalizes the entire plugin - * Loads options for each registered module and then initializes it if it's active - */ - public function action_init() { - $this->load_modules(); - - // Load all of the module options - $this->load_module_options(); - - // Load all of the modules that are enabled. - // Modules won't have an options value if they aren't enabled - foreach ( $this->modules as $mod_name => $mod_data ) { - $this->$mod_name->init(); - } - } - /** * Initialize the plugin for the admin */ @@ -150,152 +33,20 @@ public function action_admin_init() { // Upgrade if need be but don't run the upgrade if the plugin has never been used $previous_version = get_option( $this->options_group . 'version' ); if ( $previous_version && version_compare( $previous_version, VIP_WORKFLOW_VERSION, '<' ) ) { - foreach ( $this->modules as $mod_name => $mod_data ) { - if ( method_exists( $this->$mod_name, 'upgrade' ) ) { - $this->$mod_name->upgrade( $previous_version ); - } - } update_option( $this->options_group . 'version', VIP_WORKFLOW_VERSION ); } elseif ( ! $previous_version ) { update_option( $this->options_group . 'version', VIP_WORKFLOW_VERSION ); } - - // For each module that's been loaded, auto-load data if it's never been run before - foreach ( $this->modules as $mod_name => $mod_data ) { - // If the module has never been loaded before, run the install method if there is one - if ( ! isset( $mod_data->options->loaded_once ) || ! $mod_data->options->loaded_once ) { - if ( method_exists( $this->$mod_name, 'install' ) ) { - $this->$mod_name->install(); - } - $this->update_module_option( $mod_name, 'loaded_once', true ); - } - } - } - - /** - * Register a new module - */ - public function register_module( $name, $args = [] ) { - $defaults = [ - 'slug' => '', - 'post_type_support' => '', - 'default_options' => [], - 'options' => false, - 'configure_page_cb' => false, - ]; - if ( isset( $args['messages'] ) ) { - $args['messages'] = array_merge( (array) $args['messages'], $defaults['messages'] ); - } - $args = array_merge( $defaults, $args ); - $args['name'] = $name; - $args['options_group_name'] = $this->options_group . $name . '_options'; - if ( ! isset( $args['settings_slug'] ) ) { - $args['settings_slug'] = 'vw-' . $args['slug']; - } - if ( empty( $args['post_type_support'] ) ) { - $args['post_type_support'] = 'vw_' . $name; - } - - $this->modules->$name = (object) $args; - - ++$this->modules_count; - - return $this->modules->$name; - } - - /** - * Load all of the module options from the database - * If a given option isn't yet set, then set it to the module's default (upgrades, etc.) - */ - public function load_module_options() { - - foreach ( $this->modules as $mod_name => $mod_data ) { - - $this->modules->$mod_name->options = get_option( $this->options_group . $mod_name . '_options', new stdClass() ); - foreach ( $mod_data->default_options as $default_key => $default_value ) { - if ( ! isset( $this->modules->$mod_name->options->$default_key ) ) { - $this->modules->$mod_name->options->$default_key = $default_value; - } - } - - $this->$mod_name->module = $this->modules->$mod_name; - } - } - - /** - * Load the post type options again so we give add_post_type_support() a chance to work - */ - public function action_init_after() { - foreach ( $this->modules as $mod_name => $mod_data ) { - - if ( isset( $this->modules->$mod_name->options->post_types ) ) { - $this->modules->$mod_name->options->post_types = $this->helpers->clean_post_type_options( $this->modules->$mod_name->options->post_types, $mod_data->post_type_support ); - } - - $this->$mod_name->module = $this->modules->$mod_name; - } - } - - /** - * Get a module by one of its descriptive values - * - * @param string $key The property to use for searching a module (ex: 'name') - * @param string|int|array $value The value to compare (using ==) - */ - public function get_module_by( $key, $value ) { - $module = false; - foreach ( $this->modules as $mod_name => $mod_data ) { - - if ( 'name' === $key && $value === $mod_name ) { - $module = $this->modules->$mod_name; - } else { - foreach ( $mod_data as $mod_data_key => $mod_data_value ) { - if ( $mod_data_key === $key && $mod_data_value === $value ) { - $module = $this->modules->$mod_name; - } - } - } - } - return $module; } - /** - * Update a module option, using the module's name and the key - * - * @param string $mod_name The module name - * @param string $key The option key - * @param mixed $value The new value - */ - public function update_module_option( $mod_name, $key, $value ) { - $this->modules->$mod_name->options->$key = $value; - $this->$mod_name->module = $this->modules->$mod_name; - return update_option( $this->options_group . $mod_name . '_options', $this->modules->$mod_name->options ); - } - - /** - * Update all module options - * - * @param Sttring $mod_name The module name - * @param stdClass $new_options The new options to save - */ - public function update_all_module_options( $mod_name, $new_options ) { - if ( is_array( $new_options ) ) { - $new_options = (object) $new_options; - } - $this->modules->$mod_name->options = $new_options; - $this->$mod_name->module = $this->modules->$mod_name; - return update_option( $this->options_group . $mod_name . '_options', $this->modules->$mod_name->options ); - } - - /** * Collect all of the active post types * * @return array $post_types All of the post types that are 'on' */ - public function get_supported_post_types(): array { + public static function get_supported_post_types(): array { $post_types = []; - $post_types_options = $this->settings->module->options->post_types; + $post_types_options = OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'post_types'); foreach ( $post_types_options as $post_type => $value ) { if ( 'on' === $value ) { diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 4843d5b7..01766160 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -18,10 +18,11 @@ use VIPWorkflow\Modules\Shared\PHP\InstallUtilities; use VIPWorkflow\Modules\CustomStatus\REST\CustomStatusEndpoint; use VIPWorkflow\VIP_Workflow; -use WP_Error; -use WP_Query; use function VIPWorkflow\Modules\Shared\PHP\_vw_wp_link_page; + +use WP_Error; +use WP_Query; use WP_Term; use WP_Post; diff --git a/modules/settings/settings.php b/modules/settings/settings.php index d7cd7d06..09051359 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -9,117 +9,94 @@ use VIPWorkflow\VIP_Workflow; use VIPWorkflow\Modules\Shared\PHP\Module; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; +use WP_Error; +use WP_Query; +use WP_Term; +use WP_Post; -class Settings extends Module { +class Settings { const SETTINGS_SLUG = 'vw-settings'; - public $module; - - /** - * Register the module with VIP Workflow but don't do anything else - */ - public function __construct() { - // Register the module with VIP Workflow - $this->module_url = $this->get_module_url( __FILE__ ); - $args = array( - 'module_url' => $this->module_url, - 'slug' => 'settings', - 'default_options' => array( - 'post_types' => [ - 'post' => 'on', - 'page' => 'on', - ], - 'publish_guard' => 'on', - 'email_address' => '', - 'webhook_url' => '', - ), - 'configure_page_cb' => 'print_default_settings', - ); - $this->module = VIP_Workflow::instance()->register_module( 'settings', $args ); - } + const DEFAULT_OPTIONS = [ + 'post_types' => [ + 'post' => 'on', + 'page' => 'on', + ], + 'publish_guard' => 'on', + 'email_address' => '', + 'webhook_url' => '', + ]; /** * Initialize the rest of the stuff in the class if the module is active */ - public function init() { - add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); + public function init(): void { + add_action( 'admin_menu', [ __CLASS__, 'add_admin_menu' ] ); - add_action( 'admin_init', array( $this, 'helper_settings_validate_and_save' ), 100 ); - add_action( 'admin_init', array( $this, 'register_settings' ) ); + add_action( 'admin_init', [ __CLASS__, 'helper_settings_validate_and_save' ], 100 ); + add_action( 'admin_init', [ __CLASS__, 'register_settings' ] ); - add_action( 'admin_print_styles', array( $this, 'action_admin_print_styles' ) ); - add_action( 'admin_print_scripts', array( $this, 'action_admin_print_scripts' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); + add_action( 'admin_enqueue_scripts', [ __CLASS__, 'action_admin_enqueue_scripts' ] ); } /** * Add the settings page to the admin menu */ - public function add_admin_menu() { + public static function add_admin_menu(): void { $menu_title = __( 'Settings', 'vip-workflow' ); - add_submenu_page( CustomStatus::SETTINGS_SLUG, $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ $this, 'render_settings_view' ] ); + add_submenu_page( CustomStatus::SETTINGS_SLUG, $menu_title, $menu_title, 'manage_options', self::SETTINGS_SLUG, [ __CLASS__, 'render_settings_view' ] ); } /** - * Add settings JS to the settings page + * Enqueue resources that we need in the admin settings page + * + * @access private */ - public function action_admin_enqueue_scripts() { + public static function action_admin_enqueue_scripts(): void { if ( VIP_Workflow::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { - wp_enqueue_script( 'vip-workflow-settings-js', $this->module_url . 'lib/settings.js', array( 'jquery' ), VIP_WORKFLOW_VERSION, true ); + $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/settings/settings.asset.php'; + wp_enqueue_script( 'vip-workflow-settings-js', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.js', $asset_file['dependencies'], $asset_file['version'], true ); + wp_enqueue_style( 'vip-workflow-settings-styles', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.css', [ ], $asset_file['version'] ); } } - /** - * Add settings styles to the settings page - */ - public function action_admin_print_styles() { - wp_enqueue_style( 'vip_workflow-settings-css', $this->module_url . 'lib/settings.css', false, VIP_WORKFLOW_VERSION ); - } - - /** - * Extra data we need on the page for transitions, etc. - */ - public function action_admin_print_scripts() { - ?> - - module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); + public static function register_settings(): void { + $settings_option = OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ); + $settings_general_option = OptionsUtilities::get_module_options_general_key(self::SETTINGS_SLUG); + + add_settings_section( $settings_general_option , false, '__return_false', $settings_option ); - add_settings_field( 'post_types', __( 'Use on these post types:', 'vip-workflow' ), [ $this, 'helper_option_custom_post_type' ], $this->module->options_group_name, $this->module->options_group_name . '_general' ); - add_settings_field( 'publish_guard', __( 'Publish Guard', 'vip-workflow' ), [ $this, 'settings_publish_guard' ], $this->module->options_group_name, $this->module->options_group_name . '_general' ); + add_settings_field( 'post_types', __( 'Use on these post types:', 'vip-workflow' ), [ __CLASS__, 'helper_option_custom_post_type' ], $settings_option, $settings_general_option ); + add_settings_field( 'publish_guard', __( 'Publish Guard', 'vip-workflow' ), [ __CLASS__, 'settings_publish_guard' ], $settings_option, $settings_general_option ); - add_settings_field( 'email_address', __( 'Email Address', 'vip-workflow' ), [ $this, 'settings_email_address' ], $this->module->options_group_name, $this->module->options_group_name . '_general' ); - add_settings_field( 'webhook_url', __( 'Webhook URL', 'vip-workflow' ), [ $this, 'settings_webhook_url' ], $this->module->options_group_name, $this->module->options_group_name . '_general' ); + add_settings_field( 'email_address', __( 'Email Address', 'vip-workflow' ), [ __CLASS__, 'settings_email_address' ], $settings_option, $settings_general_option ); + add_settings_field( 'webhook_url', __( 'Webhook URL', 'vip-workflow' ), [ __CLASS__, 'settings_webhook_url' ], $settings_option, $settings_general_option ); } /** * Adds Settings page for VIP Workflow. */ - public function render_settings_view() { + public static function render_settings_view(): void { include_once __DIR__ . '/views/settings.php'; } /** * Option for whether the publish guard feature should be enabled */ - public function settings_publish_guard() { + public static function settings_publish_guard(): void { $options = [ 'off' => __( 'Disabled', 'vip-workflow' ), 'on' => __( 'Enabled', 'vip-workflow' ), ]; - echo ''; foreach ( $options as $value => $label ) { echo ''; } echo ''; @@ -130,16 +107,16 @@ public function settings_publish_guard() { /** * Option to set an email address to send notifications to */ - public function settings_email_address() { - printf( '', esc_attr( $this->module->options_group_name ), esc_attr( $this->module->options->email_address ) ); + public static function settings_email_address(): void { + printf( '', esc_attr( OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) ), esc_attr( OptionsUtilities::get_module_option_by_key( self::SETTINGS_SLUG, 'email_address' ) ) ); printf( '

%s

', esc_html__( 'Notify via email, when posts change custom statuses.', 'vip-workflow' ) ); } /** * Option to set the Slack webhook URL */ - public function settings_webhook_url() { - printf( '', esc_attr( $this->module->options_group_name ), esc_attr( $this->module->options->webhook_url ) ); + public static function settings_webhook_url(): void { + printf( '', esc_attr( OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) ), esc_attr( OptionsUtilities::get_module_option_by_key( self::SETTINGS_SLUG, 'webhook_url' ) ) ); printf( '

%s

', esc_html__( 'Notify a webhook URL when posts change custom statuses.', 'vip-workflow' ) ); } @@ -148,7 +125,7 @@ public function settings_webhook_url() { * * @param array $args An array of arguments to pass to the function */ - public function helper_option_custom_post_type( $args = array() ) { + public static function helper_option_custom_post_type( $args = array() ): void { $all_post_types = array( 'post' => __( 'Posts' ), @@ -158,12 +135,13 @@ public function helper_option_custom_post_type( $args = array() ) { foreach ( $all_post_types as $post_type => $title ) { echo ''; echo '
'; } @@ -171,15 +149,49 @@ public function helper_option_custom_post_type( $args = array() ) { printf( '

%s

', esc_html__( 'Enable workflow custom statuses on the above post types.', 'vip-workflow' ) ); } + /** + * Cleans up the 'on' and 'off' for post types on a given module (so we don't get warnings all over) + * For every post type that doesn't explicitly have the 'on' value, turn it 'off' + * + * @param array $module_post_types Current state of post type options for the module + * @return array $normalized_post_type_options The setting for each post type, normalized based on rules + */ + private static function clean_post_type_options( $module_post_types = array() ): array { + $normalized_post_type_options = array(); + $all_post_types = array_keys( self::get_all_post_types() ); + foreach ( $all_post_types as $post_type ) { + if ( ( isset( $module_post_types[ $post_type ] ) && 'on' == $module_post_types[ $post_type ] ) ) { + $normalized_post_type_options[ $post_type ] = 'on'; + } else { + $normalized_post_type_options[ $post_type ] = 'off'; + } + } + return $normalized_post_type_options; + } + + /** + * Gets an array of allowed post types for a module + * + * @return array post-type-slug => post-type-label + */ + private static function get_all_post_types(): array { + + $allowed_post_types = array( + 'post' => __( 'Post' ), + 'page' => __( 'Page' ), + ); + return $allowed_post_types; + } + /** * Validate input from the end user */ - public function settings_validate( $new_options ) { + public static function settings_validate( $new_options ): object { // Whitelist validation for the post type options if ( ! isset( $new_options['post_types'] ) ) { $new_options['post_types'] = []; } - $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); + $new_options['post_types'] = self::clean_post_type_options( $new_options['post_types']); // Whitelist validation for the 'publish_guard' optoins if ( ! isset( $new_options['publish_guard'] ) || 'on' != $new_options['publish_guard'] ) { @@ -208,29 +220,27 @@ public function settings_validate( $new_options ) { * Validation and sanitization on the settings field * This method is called automatically/ doesn't need to be registered anywhere */ - public function helper_settings_validate_and_save() { + public static function helper_settings_validate_and_save(): bool { if ( ! isset( $_POST['action'], $_POST['_wpnonce'], $_POST['option_page'], $_POST['_wp_http_referer'], $_POST['submit'] ) || ! is_admin() ) { return false; } if ( 'update' != $_POST['action'] - || VIP_Workflow::instance()->settings->module->options_group_name != $_POST['option_page'] ) { + || OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) != $_POST['option_page'] ) { return false; } - if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), VIP_Workflow::instance()->settings->module->options_group_name . '-options' ) ) { + if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) . '-options' ) ) { wp_die( esc_html__( 'Cheatin’ uh?' ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- validation and sanitization is done in the settings_validate method - $new_options = ( isset( $_POST[ VIP_Workflow::instance()->settings->module->options_group_name ] ) ) ? $_POST[ VIP_Workflow::instance()->settings->module->options_group_name ] : array(); + $new_options = ( isset( $_POST[ OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) ] ) ) ? $_POST[ OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) ] : array(); - $new_options = VIP_Workflow::instance()->settings->settings_validate( $new_options ); + $new_options = self::settings_validate( $new_options ); - // Cast our object and save the data. - $new_options = (object) array_merge( (array) VIP_Workflow::instance()->settings->module->options, $new_options ); - VIP_Workflow::instance()->update_all_module_options( VIP_Workflow::instance()->settings->module->name, $new_options ); + OptionsUtilities::update_module_options( self::SETTINGS_SLUG, $new_options ); // Redirect back to the settings page that was submitted without any previous messages $goback = add_query_arg( 'message', 'settings-updated', remove_query_arg( array( 'message' ), wp_get_referer() ) ); diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index 557d810e..9c88b469 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -11,6 +11,9 @@ use VIPWorkflow\VIP_Workflow; class OptionsUtilities { + const OPTIONS_GROUP = 'vip_workflow_'; + const OPTIONS_GROUP_NAME = 'vip_workflow_options'; + /** * Given a module name, return a set of saved module options * @@ -22,6 +25,11 @@ public static function get_module_options( string $module_slug ): object { return get_option( $module_options_key, new stdClass() ); } + public static function get_module_option_by_key( string $module_slug, string $key ): string|array|bool { + $module_options = self::get_module_options( $module_slug ); + return $module_options->$key; + } + /** * Update a module option, using the module's name and the key * @@ -38,13 +46,28 @@ public static function update_module_option_key( string $module_slug, string $ke return update_option( $module_options_key, $module_options ); } + /** + * Update a module options, using the module's name + * + * @param string $module_slug The slug used for this module + * @param object $new_options The new options to save + * @return bool True if the options were updated, false otherwise. + */ + public static function update_module_options( string $module_slug, object $new_options ): bool { + $module_options_key = self::get_module_options_key( $module_slug ); + $old_options = self::get_module_options( $module_slug ); + $new_options = (object) array_merge( (array) $old_options, $new_options ); + + return update_option( $module_options_key, $new_options ); + } + /** * Given a module name, return the options key for the module * * @param string $module_slug The slug used for this module - * @return string Arguments encoded in base64 + * @return string the options key for the module */ - private static function get_module_options_key( string $module_slug ): string { + public static function get_module_options_key( string $module_slug ): string { // Transform module settings slug into a slugified name, e.g. 'vw-editorial-metadata' => 'editorial_metadata' $module_options_name = str_replace( 'vw-', '', $module_slug ); $module_options_name = str_replace( '-', '_', $module_options_name ); @@ -52,4 +75,16 @@ private static function get_module_options_key( string $module_slug ): string { $vip_workflow = VIP_Workflow::instance(); return sprintf( '%s%s_options', $vip_workflow->options_group, $module_options_name ); } + + /** + * Given a module name, return the options key for the module with the '_general' suffix + * + * @param string $module_slug The slug used for this module + * @return string the options key for the module with the '_general' suffix + */ + public static function get_module_options_general_key( string $module_slug ): string { + $module_options_key = self::get_module_options_key( $module_slug ); + return $module_options_key . '_general'; + } + } diff --git a/vip-workflow.php b/vip-workflow.php index dccf773e..a9d6e8c3 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -48,9 +48,11 @@ require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/install-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/options-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/meta-cleanup-utilities.php'; +require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/util.php'; // Modules require_once VIP_WORKFLOW_ROOT . '/modules/custom-status/custom-status.php'; require_once VIP_WORKFLOW_ROOT . '/modules/editorial-metadata/editorial-metadata.php'; require_once VIP_WORKFLOW_ROOT . '/modules/notifications/notifications.php'; require_once VIP_WORKFLOW_ROOT . '/modules/preview/preview.php'; +require_once VIP_WORKFLOW_ROOT . '/modules/settings/settings.php'; diff --git a/webpack.config.js b/webpack.config.js index e86c6d36..31502ec1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,7 @@ const defaultScriptsConfig = require( '@wordpress/scripts/config/webpack.config' const glob = require( 'glob' ); require( 'dotenv' ).config(); -const wpScriptsModules = [ 'custom-status', 'editorial-metadata', 'preview' ]; +const wpScriptsModules = [ 'custom-status', 'editorial-metadata', 'preview', 'settings' ]; const wpScriptsModulesGlob = wpScriptsModules.length === 1 ? wpScriptsModules[ 0 ] : `{${ wpScriptsModules.join( ',' ) }}`; From 9fe1fc462d3edf60ccb51fd3deb6b22ee5fb9245 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 4 Oct 2024 20:33:05 +1000 Subject: [PATCH 03/21] Refactor the remaining modules --- class-workflow.php | 78 --------- dist/modules/settings/settings.asset.php | 1 + dist/modules/settings/settings.js | 0 modules/custom-status/custom-status.php | 41 ++--- .../editorial-metadata/editorial-metadata.php | 7 +- modules/notifications/notifications.php | 14 +- modules/preview/preview.php | 4 +- modules/settings/lib/settings.css | 156 ------------------ modules/settings/settings.php | 57 +++---- modules/settings/views/settings.php | 10 +- modules/shared/php/class-module.php | 64 ------- modules/shared/php/helper-utilities.php | 32 ++++ modules/shared/php/options-utilities.php | 5 +- .../notifications/test-notifications.php | 57 ++++--- vip-workflow.php | 14 +- 15 files changed, 144 insertions(+), 396 deletions(-) delete mode 100644 class-workflow.php create mode 100644 dist/modules/settings/settings.asset.php create mode 100644 dist/modules/settings/settings.js delete mode 100644 modules/settings/lib/settings.css delete mode 100644 modules/shared/php/class-module.php create mode 100644 modules/shared/php/helper-utilities.php diff --git a/class-workflow.php b/class-workflow.php deleted file mode 100644 index 55b58d0a..00000000 --- a/class-workflow.php +++ /dev/null @@ -1,78 +0,0 @@ -options_group . 'version' ); - if ( $previous_version && version_compare( $previous_version, VIP_WORKFLOW_VERSION, '<' ) ) { - update_option( $this->options_group . 'version', VIP_WORKFLOW_VERSION ); - } elseif ( ! $previous_version ) { - update_option( $this->options_group . 'version', VIP_WORKFLOW_VERSION ); - } - } - - /** - * Collect all of the active post types - * - * @return array $post_types All of the post types that are 'on' - */ - public static function get_supported_post_types(): array { - $post_types = []; - $post_types_options = OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'post_types'); - - foreach ( $post_types_options as $post_type => $value ) { - if ( 'on' === $value ) { - $post_types[] = $post_type; - } - } - - return $post_types; - } - - /** - * Whether or not the current page is our settings view. Determination is based on $pagenow, $_GET['page'], and if it's settings module or not. - * - * @return bool $is_settings_view Return true if it is - */ - public static function is_settings_view_loaded( string $slug ): bool { - global $pagenow; - - // All of the settings views are based on admin.php and a $_GET['page'] parameter - if ( 'admin.php' != $pagenow || ! isset( $_GET['page'] ) ) { - return false; - } - - // The current page better be in the array of registered settings view slugs - return $_GET['page'] === $slug; - } -} - -VIP_Workflow::instance(); diff --git a/dist/modules/settings/settings.asset.php b/dist/modules/settings/settings.asset.php new file mode 100644 index 00000000..f5345333 --- /dev/null +++ b/dist/modules/settings/settings.asset.php @@ -0,0 +1 @@ + array(), 'version' => '31d6cfe0d16ae931b73c'); diff --git a/dist/modules/settings/settings.js b/dist/modules/settings/settings.js new file mode 100644 index 00000000..e69de29b diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 01766160..d76b9aa0 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -16,8 +16,9 @@ use VIPWorkflow\Modules\Shared\PHP\InstallUtilities; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use VIPWorkflow\Modules\CustomStatus\REST\CustomStatusEndpoint; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\Shared\PHP\HelperUtilities; use function VIPWorkflow\Modules\Shared\PHP\_vw_wp_link_page; @@ -105,7 +106,7 @@ public static function init(): void { */ public static function register_custom_status_taxonomy(): void { // We need to make sure taxonomy is registered for all of the post types that support it - $supported_post_types = VIP_Workflow::instance()->get_supported_post_types(); + $supported_post_types = HelperUtilities::get_supported_post_types(); register_taxonomy( self::TAXONOMY_KEY, $supported_post_types, [ @@ -168,7 +169,7 @@ public static function disable_custom_statuses_for_post_type( ?string $post_type $post_type = self::get_current_post_type(); } - $supported_post_types = VIP_Workflow::instance()->get_supported_post_types(); + $supported_post_types = HelperUtilities::get_supported_post_types(); if ( $post_type && ! in_array( $post_type, $supported_post_types ) ) { return true; @@ -282,7 +283,7 @@ public static function render_settings_view(): void { */ public static function action_admin_enqueue_scripts(): void { // Load Javascript we need to use on the configuration views - if ( VIP_Workflow::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { + if ( Settings::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status-configure.asset.php'; wp_enqueue_script( 'vip-workflow-custom-status-configure', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-configure.js', $asset_file['dependencies'], $asset_file['version'], true ); wp_enqueue_style( 'vip-workflow-custom-status-styles', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-configure.css', [ 'wp-components' ], $asset_file['version'] ); @@ -323,13 +324,13 @@ public static function load_scripts_for_block_editor(): void { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status-block.asset.php'; wp_enqueue_script( 'vip-workflow-block-custom-status-script', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-block.js', $asset_file['dependencies'], $asset_file['version'], true ); - $publish_guard_enabled = ( 'on' === VIP_Workflow::instance()->settings->module->options->publish_guard ) ? true : false; + $publish_guard_enabled = ( 'on' === OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'publish_guard' ) ) ? true : false; wp_localize_script( 'vip-workflow-block-custom-status-script', 'VW_CUSTOM_STATUSES', [ 'current_user_id' => get_current_user_id(), 'is_publish_guard_enabled' => $publish_guard_enabled, 'status_terms' => self::get_custom_statuses(), - 'supported_post_types' => VIP_Workflow::instance()->get_supported_post_types(), + 'supported_post_types' => HelperUtilities::get_supported_post_types(), ] ); } @@ -352,7 +353,7 @@ public static function is_whitelisted_page(): bool { $current_post_type = self::get_current_post_type(); - if ( ! in_array( $current_post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { + if ( ! in_array( $current_post_type, HelperUtilities::get_supported_post_types() ) ) { return false; } @@ -449,7 +450,7 @@ public static function maybe_block_post_update( array $data ): array|bool { $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); // Ignore if it's not a post status and post type we support - if ( ! in_array( $data['post_type'], VIP_Workflow::instance()->get_supported_post_types() ) ) { + if ( ! in_array( $data['post_type'], HelperUtilities::get_supported_post_types() ) ) { return $data; } @@ -498,12 +499,12 @@ public static function remove_or_add_publish_capability_for_user( array $allcaps ]; // Bail early if publish guard is off, or the post is already published, or the post is not available - if ( 'off' === VIP_Workflow::instance()->settings->module->options->publish_guard || ! $post || 'publish' === $post->post_status ) { + if ( 'off' === OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'publish_guard' ) || ! $post || 'publish' === $post->post_status ) { return $allcaps; } // Bail early if the post type is not supported or if its a not supported capability for this guard - if ( ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) || ! isset( $supported_publish_caps_map[ $post->post_type ] ) ) { + if ( ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) || ! isset( $supported_publish_caps_map[ $post->post_type ] ) ) { return $allcaps; } @@ -879,7 +880,7 @@ public static function get_custom_status_by( string $field, int|string $value ): */ public static function reassign_post_status( string $old_status, string $new_status ): bool|WP_Error { $old_status_post_ids = ( new WP_Query( [ - 'post_type' => VIP_Workflow::instance()->get_supported_post_types(), + 'post_type' => HelperUtilities::get_supported_post_types(), 'post_status' => $old_status, 'posts_per_page' => -1, 'fields' => 'ids', @@ -929,7 +930,7 @@ public static function reassign_post_status( string $old_status, string $new_sta * @return array $post_states */ public static function add_status_to_post_states( array $post_states, WP_Post $post ): array { - if ( ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types(), true ) ) { + if ( ! in_array( $post->post_type, HelperUtilities::get_supported_post_types(), true ) ) { // Return early if this post type doesn't support custom statuses. return $post_states; } @@ -993,7 +994,7 @@ public static function is_post_using_custom_status( int $post_id ): bool { return false; } - $custom_post_types = VIP_Workflow::instance()->get_supported_post_types(); + $custom_post_types = HelperUtilities::get_supported_post_types(); $custom_statuses = self::get_custom_statuses(); $status_slugs = wp_list_pluck( $custom_statuses, 'slug' ); @@ -1019,7 +1020,7 @@ public static function maybe_keep_post_name_empty( array $data, array $postarr ) // Ignore if it's not a post status and post type we support if ( ! in_array( $data['post_status'], $status_slugs ) - || ! in_array( $data['post_type'], VIP_Workflow::instance()->get_supported_post_types() ) ) { + || ! in_array( $data['post_type'], HelperUtilities::get_supported_post_types() ) ) { return $data; } @@ -1055,7 +1056,7 @@ public static function fix_unique_post_slug( string|null $override_slug, string $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! in_array( $post_status, $status_slugs ) - || ! in_array( $post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { + || ! in_array( $post_type, HelperUtilities::get_supported_post_types() ) ) { return null; } @@ -1092,7 +1093,7 @@ public static function fix_preview_link_part_one( string $preview_link ): string || ! is_admin() || 'post.php' != $pagenow || ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) || strpos( $preview_link, 'preview_id' ) !== false || 'sample' === $post->filter ) { return $preview_link; @@ -1122,7 +1123,7 @@ public static function fix_preview_link_part_two( string $permalink, int|WP_Post } //Should we be doing anything at all? - if ( ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { + if ( ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { return $permalink; } @@ -1195,7 +1196,7 @@ public static function fix_get_sample_permalink( array $permalink, int $post_id, $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { return $permalink; } @@ -1238,7 +1239,7 @@ public static function fix_get_sample_permalink_html( array $permalink, int $pos $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { return $permalink; } @@ -1333,7 +1334,7 @@ public static function fix_post_row_actions( array $actions, WP_Post $post ): ar $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); if ( 'edit.php' != $pagenow || ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, VIP_Workflow::instance()->get_supported_post_types() ) ) { + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { return $actions; } diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index b9050a8c..5a920a93 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -9,6 +9,7 @@ require_once __DIR__ . '/rest/editorial-metadata-endpoint.php'; use VIPWorkflow\Modules\EditorialMetadata\REST\EditorialMetadataEndpoint; +use VIPWorkflow\Modules\Shared\PHP\HelperUtilities; use VIPWorkflow\Modules\Shared\PHP\InstallUtilities; use VIPWorkflow\VIP_Workflow; use WP_Error; @@ -63,7 +64,7 @@ public static function register_editorial_metadata_terms_as_post_meta(): void { $post_meta_key = $term->meta[ self::METADATA_POSTMETA_KEY ]; $post_meta_args = self::get_postmeta_args( $term ); - foreach ( VIP_Workflow::instance()->get_supported_post_types() as $post_type ) { + foreach ( HelperUtilities::get_supported_post_types() as $post_type ) { register_post_meta( $post_type, $post_meta_key, $post_meta_args ); } } @@ -76,7 +77,7 @@ public static function register_editorial_metadata_terms_as_post_meta(): void { */ public static function register_editorial_metadata_taxonomy(): void { // We need to make sure taxonomy is registered for all of the post types that support it - $supported_post_types = VIP_Workflow::instance()->get_supported_post_types(); + $supported_post_types = HelperUtilities::get_supported_post_types(); register_taxonomy( self::METADATA_TAXONOMY, $supported_post_types, [ @@ -161,7 +162,7 @@ public static function render_settings_view(): void { */ public static function action_admin_enqueue_scripts(): void { // Load Javascript we need to use on the configuration views - if ( VIP_Workflow::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { + if ( Settings::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/editorial-metadata/editorial-metadata-configure.asset.php'; wp_enqueue_script( 'vip-workflow-editorial-metadata-configure', VIP_WORKFLOW_URL . 'dist/modules/editorial-metadata/editorial-metadata-configure.js', $asset_file['dependencies'], $asset_file['version'], true ); wp_enqueue_style( 'vip-workflow-editorial-metadata-styles', VIP_WORKFLOW_URL . 'dist/modules/editorial-metadata/editorial-metadata-configure.css', [ 'wp-components' ], $asset_file['version'] ); diff --git a/modules/notifications/notifications.php b/modules/notifications/notifications.php index 9c338433..0cf802bf 100644 --- a/modules/notifications/notifications.php +++ b/modules/notifications/notifications.php @@ -8,7 +8,9 @@ use WP_Post; -use VIPWorkflow\VIP_Workflow; +use VIPWorkflow\Modules\Shared\PHP\HelperUtilities; +use VIPWorkflow\Modules\Settings; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use function VIPWorkflow\Modules\Shared\PHP\vw_draft_or_post_title; class Notifications { @@ -31,7 +33,7 @@ public static function init(): void { * Set up and send post status change notification email */ public static function notification_status_change( string $new_status, string $old_status, WP_Post $post ): void { - $supported_post_types = VIP_Workflow::instance()->get_supported_post_types(); + $supported_post_types = HelperUtilities::get_supported_post_types(); if ( ! in_array( $post->post_type, $supported_post_types ) ) { return; } @@ -179,11 +181,11 @@ public static function get_notification_footer(): string { */ public static function schedule_emails( string $action, WP_Post $post, string $subject, string $message, string $message_headers = '' ): void { // Ensure the email address is set from settings. - if ( empty( VIP_Workflow::instance()->settings->module->options->email_address ) ) { + if ( empty( OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'email_address' ) ) ) { return; } - $email_recipients = [ VIP_Workflow::instance()->settings->module->options->email_address ]; + $email_recipients = [ OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'email_address' ) ]; /** * Filter the email recipients @@ -252,7 +254,7 @@ public static function send_emails( array $recipients, string $subject, string $ */ public static function schedule_webhook_notification( string $webhook_message, string $action, string $timestamp ): void { // Ensure the webhook URL is set from settings. - if ( empty( VIP_Workflow::instance()->settings->module->options->webhook_url ) ) { + if ( empty( OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'webhook_url' ) ) ) { return; } @@ -270,7 +272,7 @@ public static function schedule_webhook_notification( string $webhook_message, s * @return bool True if the notification was sent successfully, false otherwise */ public static function send_to_webhook( string $message, string $message_type, string $timestamp ): bool { - $webhook_url = VIP_Workflow::instance()->settings->module->options->webhook_url; + $webhook_url = OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'webhook_url' ); // Set up the payload $payload = [ diff --git a/modules/preview/preview.php b/modules/preview/preview.php index 6e6262fa..a6d2b762 100644 --- a/modules/preview/preview.php +++ b/modules/preview/preview.php @@ -12,8 +12,8 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Preview\PreviewEndpoint; -use VIPWorkflow\VIP_Workflow; use VIPWorkflow\Modules\Preview\Token; +use VIPWorkflow\Modules\Shared\PHP\HelperUtilities; use WP_Query; class Preview { @@ -45,7 +45,7 @@ public static function load_block_editor_scripts(): void { $generate_preview_url = PreviewEndpoint::get_url( $post_id ); } $custom_status_slugs = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); - $custom_post_types = VIP_Workflow::instance()->get_supported_post_types(); + $custom_post_types = HelperUtilities::get_supported_post_types(); wp_localize_script( 'vip-workflow-preview-script', 'VW_PREVIEW', [ 'custom_post_types' => $custom_post_types, diff --git a/modules/settings/lib/settings.css b/modules/settings/lib/settings.css deleted file mode 100644 index e0a73eae..00000000 --- a/modules/settings/lib/settings.css +++ /dev/null @@ -1,156 +0,0 @@ -/* @override http://localhost:8888/wpplugindev/wp-content/plugins/vip-workflow/css/settings.css?ver=0.6.4 */ - -.float-right { - float: right; -} - -.float-left { - float: left; -} - -.clear-left { - clear: left; -} - -.clear-right { - clear: right; -} - -.clear-both { - clear: both; -} - -.vip-workflow-modules { - width: 100%; - overflow: hidden; - margin-top: 20px; - display: flex; - flex-wrap: wrap; -} - -.vip-workflow-modules .vip-workflow-module { - width: 250px; - min-height: 150px; - position: relative; - float: left; - border: 1px solid #e1e1e1; - padding: 10px; - background-color: #f1f1f1; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - margin-right: 15px; - margin-bottom: 15px; -} - -.vip-workflow-modules .vip-workflow-module p { - font-size: 12px; - color: #3d3d3d; - line-height: 150; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", Verdana, "Bitstream Vera Sans", sans-serif; -} - -.vip-workflow-modules .vip-workflow-module h4 { - color: #000; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", Verdana, "Bitstream Vera Sans", sans-serif; - font-size: 17px; - font-style: normal; - font-weight: 400; - line-height: 1.1; - margin: 0; - padding: 0; -} - -.vip-workflow-modules .vip-workflow-module .vip-workflow-module-actions { - position: absolute; - bottom: 0; - left: 10px; - width: 250px; -} - -.vip-workflow-modules .vip-workflow-module .button-primary:hover { - color: #fff; - border-color: #3b5d0c; -} - -.vip-workflow-modules .vip-workflow-module .button-primary.configure-vip-workflow-module { - color: #f1f1f1; - border: solid 1px #666; - background: #ddd; - background: -webkit-gradient(linear, left top, left bottom, from(#bbb), to(#888)); - background: -moz-linear-gradient(top, #bbb, #888); - box-shadow: none; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#BBB", endColorstr="#888"); - text-shadow: none; - float: right; -} - -.vip-workflow-modules .vip-workflow-module .button-primary.enable-disable-vip-workflow-module { - float: left; -} - -.vip-workflow-modules .vip-workflow-module .button-primary.configure-vip-workflow-module:hover { - color: #fff; - border-color: #444; -} - -.vip-workflow-admin a.cancel-settings-link { - margin-left: 10px; -} - -.vip-workflow-admin .explanation, -.vip-workflow-admin .explanation p { - font-size: 16px; - line-height: 22px; -} - -.vip-workflow-admin .explanation { - margin-bottom: 1em; -} - -.vip-workflow-admin .explanation p { - margin: 0.5em 0; -} - -/* Module specific image styles */ - -.button-inline-block { - display: inline-block; -} - -h3.nav-tab-wrapper { - margin-top: 0; -} - -.vip-workflow-message { - position: relative; - top: -5px; - margin-left: 25px; - font-weight: 400; - font-size: 12px; - line-height: 20px; - padding-left: 25px; - padding-right: 25px; - padding-top: 5px; - padding-bottom: 4px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} - -.vip-workflow-updated-message { - background-color: #ffffe0; - border: 1px solid #e6db55; - transition: opacity 0.5s ease; -} - -.vip-workflow-error-message { - background-color: #ffebe8; - border: 1px solid #c00; -} - -/* Admin Menu Icon */ -#adminmenu .toplevel_page_vw-settings .wp-menu-image img { - width: 16px; - height: 16px; -} diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 09051359..02375942 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -8,30 +8,15 @@ namespace VIPWorkflow\Modules; use VIPWorkflow\VIP_Workflow; -use VIPWorkflow\Modules\Shared\PHP\Module; use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; -use WP_Error; -use WP_Query; -use WP_Term; -use WP_Post; class Settings { const SETTINGS_SLUG = 'vw-settings'; - const DEFAULT_OPTIONS = [ - 'post_types' => [ - 'post' => 'on', - 'page' => 'on', - ], - 'publish_guard' => 'on', - 'email_address' => '', - 'webhook_url' => '', - ]; - /** * Initialize the rest of the stuff in the class if the module is active */ - public function init(): void { + public static function init(): void { add_action( 'admin_menu', [ __CLASS__, 'add_admin_menu' ] ); add_action( 'admin_init', [ __CLASS__, 'helper_settings_validate_and_save' ], 100 ); @@ -55,10 +40,9 @@ public static function add_admin_menu(): void { * @access private */ public static function action_admin_enqueue_scripts(): void { - if ( VIP_Workflow::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { + if ( self::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/settings/settings.asset.php'; wp_enqueue_script( 'vip-workflow-settings-js', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.js', $asset_file['dependencies'], $asset_file['version'], true ); - wp_enqueue_style( 'vip-workflow-settings-styles', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.css', [ ], $asset_file['version'] ); } } @@ -67,9 +51,9 @@ public static function action_admin_enqueue_scripts(): void { */ public static function register_settings(): void { $settings_option = OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ); - $settings_general_option = OptionsUtilities::get_module_options_general_key(self::SETTINGS_SLUG); + $settings_general_option = OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ); - add_settings_section( $settings_general_option , false, '__return_false', $settings_option ); + add_settings_section( $settings_general_option, false, '__return_false', $settings_option ); add_settings_field( 'post_types', __( 'Use on these post types:', 'vip-workflow' ), [ __CLASS__, 'helper_option_custom_post_type' ], $settings_option, $settings_general_option ); add_settings_field( 'publish_guard', __( 'Publish Guard', 'vip-workflow' ), [ __CLASS__, 'settings_publish_guard' ], $settings_option, $settings_general_option ); @@ -96,7 +80,7 @@ public static function settings_publish_guard(): void { echo ''; @@ -136,12 +120,10 @@ public static function helper_option_custom_post_type( $args = array() ): void { echo ''; echo '
'; } @@ -149,6 +131,23 @@ public static function helper_option_custom_post_type( $args = array() ): void { printf( '

%s

', esc_html__( 'Enable workflow custom statuses on the above post types.', 'vip-workflow' ) ); } + /** + * Whether or not the current page is our settings view. Determination is based on $pagenow, $_GET['page'], and if it's settings module or not. + * + * @return bool $is_settings_view Return true if it is + */ + public static function is_settings_view_loaded( string $slug ): bool { + global $pagenow; + + // All of the settings views are based on admin.php and a $_GET['page'] parameter + if ( 'admin.php' != $pagenow || ! isset( $_GET['page'] ) ) { + return false; + } + + // The current page better be in the array of registered settings view slugs + return $_GET['page'] === $slug; + } + /** * Cleans up the 'on' and 'off' for post types on a given module (so we don't get warnings all over) * For every post type that doesn't explicitly have the 'on' value, turn it 'off' @@ -191,7 +190,7 @@ public static function settings_validate( $new_options ): object { if ( ! isset( $new_options['post_types'] ) ) { $new_options['post_types'] = []; } - $new_options['post_types'] = self::clean_post_type_options( $new_options['post_types']); + $new_options['post_types'] = self::clean_post_type_options( $new_options['post_types'] ); // Whitelist validation for the 'publish_guard' optoins if ( ! isset( $new_options['publish_guard'] ) || 'on' != $new_options['publish_guard'] ) { @@ -227,16 +226,16 @@ public static function helper_settings_validate_and_save(): bool { } if ( 'update' != $_POST['action'] - || OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) != $_POST['option_page'] ) { + || OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) != $_POST['option_page'] ) { return false; } - if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) . '-options' ) ) { + if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) . '-options' ) ) { wp_die( esc_html__( 'Cheatin’ uh?' ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- validation and sanitization is done in the settings_validate method - $new_options = ( isset( $_POST[ OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) ] ) ) ? $_POST[ OptionsUtilities::get_module_options_key(self::SETTINGS_SLUG) ] : array(); + $new_options = ( isset( $_POST[ OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) ] ) ) ? $_POST[ OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) ] : array(); $new_options = self::settings_validate( $new_options ); @@ -248,3 +247,5 @@ public static function helper_settings_validate_and_save(): bool { exit; } } + +Settings::init(); diff --git a/modules/settings/views/settings.php b/modules/settings/views/settings.php index 5a277e18..b7f5ec7e 100644 --- a/modules/settings/views/settings.php +++ b/modules/settings/views/settings.php @@ -1,5 +1,8 @@ -
- module->options_group_name ); ?> - module->options_group_name ); ?> - + + +

diff --git a/modules/shared/php/class-module.php b/modules/shared/php/class-module.php deleted file mode 100644 index 4637aa61..00000000 --- a/modules/shared/php/class-module.php +++ /dev/null @@ -1,64 +0,0 @@ - post-type-label - */ - public function get_all_post_types() { - - $allowed_post_types = array( - 'post' => __( 'Post' ), - 'page' => __( 'Page' ), - ); - return $allowed_post_types; - } - - /** - * Cleans up the 'on' and 'off' for post types on a given module (so we don't get warnings all over) - * For every post type that doesn't explicitly have the 'on' value, turn it 'off' - * - * @param array $module_post_types Current state of post type options for the module - * @return array $normalized_post_type_options The setting for each post type, normalized based on rules - */ - public function clean_post_type_options( $module_post_types = array() ) { - $normalized_post_type_options = array(); - $all_post_types = array_keys( $this->get_all_post_types() ); - foreach ( $all_post_types as $post_type ) { - if ( ( isset( $module_post_types[ $post_type ] ) && 'on' == $module_post_types[ $post_type ] ) ) { - $normalized_post_type_options[ $post_type ] = 'on'; - } else { - $normalized_post_type_options[ $post_type ] = 'off'; - } - } - return $normalized_post_type_options; - } - - /** - * Get the publicly accessible URL for the module based on the filename - * - * @param string $filepath File path for the module - * @return string $module_url Publicly accessible URL for the module - */ - public function get_module_url( $file ) { - $module_url = plugins_url( '/', $file ); - return trailingslashit( $module_url ); - } -} diff --git a/modules/shared/php/helper-utilities.php b/modules/shared/php/helper-utilities.php new file mode 100644 index 00000000..b999f4ab --- /dev/null +++ b/modules/shared/php/helper-utilities.php @@ -0,0 +1,32 @@ + $value ) { + if ( 'on' === $value ) { + $post_types[] = $post_type; + } + } + + return $post_types; + } +} diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index 9c88b469..76b0c973 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -8,7 +8,6 @@ namespace VIPWorkflow\Modules\Shared\PHP; use stdClass; -use VIPWorkflow\VIP_Workflow; class OptionsUtilities { const OPTIONS_GROUP = 'vip_workflow_'; @@ -72,8 +71,7 @@ public static function get_module_options_key( string $module_slug ): string { $module_options_name = str_replace( 'vw-', '', $module_slug ); $module_options_name = str_replace( '-', '_', $module_options_name ); - $vip_workflow = VIP_Workflow::instance(); - return sprintf( '%s%s_options', $vip_workflow->options_group, $module_options_name ); + return sprintf( '%s%s_options', self::OPTIONS_GROUP, $module_options_name ); } /** @@ -86,5 +84,4 @@ public static function get_module_options_general_key( string $module_slug ): st $module_options_key = self::get_module_options_key( $module_slug ); return $module_options_key . '_general'; } - } diff --git a/tests/modules/notifications/test-notifications.php b/tests/modules/notifications/test-notifications.php index 7472706c..5aa985a0 100644 --- a/tests/modules/notifications/test-notifications.php +++ b/tests/modules/notifications/test-notifications.php @@ -8,7 +8,6 @@ namespace VIPWorkflow\Tests; -use VIPWorkflow\VIP_Workflow; use VIPWorkflow\Modules\Notifications; use WP_Error; use WP_UnitTestCase; @@ -44,41 +43,41 @@ public function test_send_emails() { $this->assertDiscardWhitespace( $body, $email->body ); } - public function test_send_to_webhook_happy_path() { - // Hook in and return a known response - add_filter( 'pre_http_request', function () { - return array( - 'headers' => array(), - 'cookies' => array(), - 'filename' => null, - 'response' => 200, - 'status_code' => 200, - 'success' => 1, - 'body' => 'All Done', - ); - }, 10, 3 ); + // public function test_send_to_webhook_happy_path() { + // // Hook in and return a known response + // add_filter( 'pre_http_request', function () { + // return array( + // 'headers' => array(), + // 'cookies' => array(), + // 'filename' => null, + // 'response' => 200, + // 'status_code' => 200, + // 'success' => 1, + // 'body' => 'All Done', + // ); + // }, 10, 3 ); - VIP_Workflow::instance()->settings->module->options->webhook_url = 'https://webhook.site/this-url-doesnt-exist'; + // VIP_Workflow::instance()->settings->module->options->webhook_url = 'https://webhook.site/this-url-doesnt-exist'; - $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); + // $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); - $this->assertTrue( $response ); + // $this->assertTrue( $response ); - VIP_Workflow::instance()->settings->module->options->webhook_url = ''; - } + // VIP_Workflow::instance()->settings->module->options->webhook_url = ''; + // } - public function test_send_to_webhook_error_path() { - // Hook in and return a known response - add_filter( 'pre_http_request', function () { - return new WP_Error( 'http_request_failed', 'Error Message' ); - }, 10, 3 ); + // public function test_send_to_webhook_error_path() { + // // Hook in and return a known response + // add_filter( 'pre_http_request', function () { + // return new WP_Error( 'http_request_failed', 'Error Message' ); + // }, 10, 3 ); - VIP_Workflow::instance()->settings->module->options->webhook_url = 'https://webhook.site/this-url-doesnt-exist'; + // VIP_Workflow::instance()->settings->module->options->webhook_url = 'https://webhook.site/this-url-doesnt-exist'; - $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); + // $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); - $this->assertFalse( $response ); + // $this->assertFalse( $response ); - VIP_Workflow::instance()->settings->module->options->webhook_url = ''; - } + // VIP_Workflow::instance()->settings->module->options->webhook_url = ''; + // } } diff --git a/vip-workflow.php b/vip-workflow.php index a9d6e8c3..f8366c6b 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -41,10 +41,20 @@ define( 'VIP_WORKFLOW_SETTINGS_PAGE', add_query_arg( 'page', 'vw-settings', get_admin_url( null, 'admin.php' ) ) ); define( 'VIP_WORKFLOW_REST_NAMESPACE', 'vip-workflow/v1' ); -// Main plugin class -require_once VIP_WORKFLOW_ROOT . '/class-workflow.php'; + +// Set the version for the plugin +// It's not used for anything, which is why it's here. +add_action( 'admin_init', function () { + $previous_version = get_option( 'vip_workflow_version' ); + if ( $previous_version && version_compare( $previous_version, VIP_WORKFLOW_VERSION, '<' ) ) { + update_option( 'vip_workflow_version', VIP_WORKFLOW_VERSION ); + } elseif ( ! $previous_version ) { + update_option( 'vip_workflow_version', VIP_WORKFLOW_VERSION ); + } +} ); // Utility classes +require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/helper-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/install-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/options-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/meta-cleanup-utilities.php'; From e653884317c3c569c2f513a1e7a8334d4c73adc7 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 4 Oct 2024 20:42:41 +1000 Subject: [PATCH 04/21] Fix missing imports --- modules/custom-status/custom-status.php | 2 +- modules/settings/settings.php | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index d76b9aa0..16256a19 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -499,7 +499,7 @@ public static function remove_or_add_publish_capability_for_user( array $allcaps ]; // Bail early if publish guard is off, or the post is already published, or the post is not available - if ( 'off' === OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'publish_guard' ) || ! $post || 'publish' === $post->post_status ) { + if ( ! $post || 'off' === OptionsUtilities::get_module_option_by_key( Settings::SETTINGS_SLUG, 'publish_guard' ) || 'publish' === $post->post_status ) { return $allcaps; } diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 02375942..49e4014b 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -50,16 +50,13 @@ public static function action_admin_enqueue_scripts(): void { * Register the settings for the module */ public static function register_settings(): void { - $settings_option = OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ); - $settings_general_option = OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ); + add_settings_section( OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ), false, '__return_false', OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ) ); - add_settings_section( $settings_general_option, false, '__return_false', $settings_option ); + add_settings_field( 'post_types', __( 'Use on these post types:', 'vip-workflow' ), [ __CLASS__, 'helper_option_custom_post_type' ], OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ), OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ) ); + add_settings_field( 'publish_guard', __( 'Publish Guard', 'vip-workflow' ), [ __CLASS__, 'settings_publish_guard' ], OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ), OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ) ); - add_settings_field( 'post_types', __( 'Use on these post types:', 'vip-workflow' ), [ __CLASS__, 'helper_option_custom_post_type' ], $settings_option, $settings_general_option ); - add_settings_field( 'publish_guard', __( 'Publish Guard', 'vip-workflow' ), [ __CLASS__, 'settings_publish_guard' ], $settings_option, $settings_general_option ); - - add_settings_field( 'email_address', __( 'Email Address', 'vip-workflow' ), [ __CLASS__, 'settings_email_address' ], $settings_option, $settings_general_option ); - add_settings_field( 'webhook_url', __( 'Webhook URL', 'vip-workflow' ), [ __CLASS__, 'settings_webhook_url' ], $settings_option, $settings_general_option ); + add_settings_field( 'email_address', __( 'Email Address', 'vip-workflow' ), [ __CLASS__, 'settings_email_address' ], OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ), OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ) ); + add_settings_field( 'webhook_url', __( 'Webhook URL', 'vip-workflow' ), [ __CLASS__, 'settings_webhook_url' ], OptionsUtilities::get_module_options_key( self::SETTINGS_SLUG ), OptionsUtilities::get_module_options_general_key( self::SETTINGS_SLUG ) ); } /** From 2ccbe18d5faeab2fe1a0d096fb6d4237fdf1d722 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 4 Oct 2024 21:07:07 +1000 Subject: [PATCH 05/21] Fix the settings not showing up --- modules/settings/settings.php | 5 +++-- modules/settings/views/settings.php | 4 ++-- modules/shared/php/options-utilities.php | 2 +- vip-workflow.php | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 49e4014b..a91f8fc9 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -42,7 +42,8 @@ public static function add_admin_menu(): void { public static function action_admin_enqueue_scripts(): void { if ( self::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/settings/settings.asset.php'; - wp_enqueue_script( 'vip-workflow-settings-js', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.js', $asset_file['dependencies'], $asset_file['version'], true ); + $dependencies = [ ...$asset_file['dependencies'], 'jquery' ]; + wp_enqueue_script( 'vip-workflow-settings-js', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.js', $dependencies, $asset_file['version'], true ); } } @@ -182,7 +183,7 @@ private static function get_all_post_types(): array { /** * Validate input from the end user */ - public static function settings_validate( $new_options ): object { + public static function settings_validate( $new_options ): array { // Whitelist validation for the post type options if ( ! isset( $new_options['post_types'] ) ) { $new_options['post_types'] = []; diff --git a/modules/settings/views/settings.php b/modules/settings/views/settings.php index b7f5ec7e..fc9a7213 100644 --- a/modules/settings/views/settings.php +++ b/modules/settings/views/settings.php @@ -27,8 +27,8 @@
- - + +

diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index 76b0c973..b5badc6f 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -52,7 +52,7 @@ public static function update_module_option_key( string $module_slug, string $ke * @param object $new_options The new options to save * @return bool True if the options were updated, false otherwise. */ - public static function update_module_options( string $module_slug, object $new_options ): bool { + public static function update_module_options( string $module_slug, array $new_options ): bool { $module_options_key = self::get_module_options_key( $module_slug ); $old_options = self::get_module_options( $module_slug ); $new_options = (object) array_merge( (array) $old_options, $new_options ); diff --git a/vip-workflow.php b/vip-workflow.php index f8366c6b..0235691e 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -61,8 +61,8 @@ require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/util.php'; // Modules +require_once VIP_WORKFLOW_ROOT . '/modules/settings/settings.php'; require_once VIP_WORKFLOW_ROOT . '/modules/custom-status/custom-status.php'; require_once VIP_WORKFLOW_ROOT . '/modules/editorial-metadata/editorial-metadata.php'; require_once VIP_WORKFLOW_ROOT . '/modules/notifications/notifications.php'; require_once VIP_WORKFLOW_ROOT . '/modules/preview/preview.php'; -require_once VIP_WORKFLOW_ROOT . '/modules/settings/settings.php'; From a7b56a23d2a58e08eba8c446f1d1687b76226ad6 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Fri, 4 Oct 2024 21:12:30 +1000 Subject: [PATCH 06/21] Fix return type --- modules/shared/php/options-utilities.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index b5badc6f..e0a49331 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -19,12 +19,12 @@ class OptionsUtilities { * @param string $module_slug The slug used for this module * @return object The set of saved module options for this module, or an empty stdClass if none are found */ - public static function get_module_options( string $module_slug ): object { + public static function get_module_options( string $module_slug ): object|null { $module_options_key = self::get_module_options_key( $module_slug ); return get_option( $module_options_key, new stdClass() ); } - public static function get_module_option_by_key( string $module_slug, string $key ): string|array|bool { + public static function get_module_option_by_key( string $module_slug, string $key ): string|array|bool|null { $module_options = self::get_module_options( $module_slug ); return $module_options->$key; } From 086e8f9d00a761d8438a8260f9d6acc43b27615b Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 14:02:30 +1100 Subject: [PATCH 07/21] Fix the default options not being set, the settings menu ordering, double status creations and the lack of settings updated message --- modules/custom-status/custom-status.php | 32 +++++++++++++----------- modules/settings/settings.php | 4 +-- modules/settings/views/settings.php | 8 +++++- modules/shared/php/options-utilities.php | 22 +++++++++++++++- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 16256a19..8119e47e 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -127,17 +127,13 @@ public static function register_custom_status_taxonomy(): void { public static function register_custom_statuses(): void { global $wp_post_statuses; - // Users can delete the pending status if they want, so let's get rid of that - // It'll get re-added if the user hasn't "deleted" them - unset( $wp_post_statuses['pending'] ); - - $custom_statuses = self::get_custom_statuses(); + $custom_statuses = self::get_custom_statuses(); - // Unfortunately, register_post_status() doesn't accept a - // post type argument, so we have to register the post - // statuses for all post types. This results in - // all post statuses for a post type appearing at the top - // of manage posts if there is a post with the status + // Unfortunately, register_post_status() doesn't accept a + // post type argument, so we have to register the post + // statuses for all post types. This results in + // all post statuses for a post type appearing at the top + // of manage posts if there is a post with the status foreach ( $custom_statuses as $status ) { register_post_status( $status->slug, [ 'label' => $status->name, @@ -642,14 +638,17 @@ public static function update_custom_status( int $status_id, array $args = [] ): // Reset our internal object cache self::$custom_statuses_cache = []; + // Don't allow changing the name or slug of the Draft or pending status as they are core statuses + $banned_statuses = [ 'draft', 'pending' ]; + // Prevent user from changing draft name or slug - if ( 'draft' === $old_status->slug + if ( in_array( $old_status->slug, $banned_statuses ) && ( ( isset( $args['name'] ) && $args['name'] !== $old_status->name ) || ( isset( $args['slug'] ) && $args['slug'] !== $old_status->slug ) ) ) { - return new WP_Error( 'invalid', __( 'Changing the name and slug of "Draft" is not allowed', 'vip-workflow' ) ); + return new WP_Error( 'restricted', __( 'Changing the name and slug of a restricted status ', 'vip-workflow' ) . '(' . $old_status->name . ') is not allowed.' ); } // If the name was changed, we need to change the slug @@ -727,8 +726,11 @@ public static function delete_custom_status( int $status_id ): bool|WP_Error { $old_status_slug = $old_status->slug; - if ( self::is_restricted_status( $old_status_slug ) || 'draft' === $old_status_slug ) { - return new WP_Error( 'restricted', __( 'Restricted status ', 'vip-workflow' ) . '(' . $old_status->name . ')' ); + // Don't allow changing the name or slug of the Draft or pending status as they are core statuses + $banned_statuses = [ 'draft', 'pending' ]; + + if ( self::is_restricted_status( $old_status_slug ) || in_array( $old_status->slug, $banned_statuses ) ) { + return new WP_Error( 'restricted', __( 'Restricted status ', 'vip-workflow' ) . '(' . $old_status->name . ') cannot be deleted.' ); } // Reset our internal object cache @@ -736,7 +738,7 @@ public static function delete_custom_status( int $status_id ): bool|WP_Error { // Get the new status to reassign posts to, which would be the first custom status. // In the event that the first custom status is being deleted, we'll reassign to the second custom status. - // Since draft cannot be deleted, we don't need to worry about ever getting index out of bounds. + // Since draft and pending review cannot be deleted, we don't need to worry about ever getting index out of bounds. $custom_statuses = self::get_custom_statuses(); $new_status_slug = $custom_statuses[0]->slug; if ( $old_status_slug === $new_status_slug ) { diff --git a/modules/settings/settings.php b/modules/settings/settings.php index a91f8fc9..14136031 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -17,7 +17,8 @@ class Settings { * Initialize the rest of the stuff in the class if the module is active */ public static function init(): void { - add_action( 'admin_menu', [ __CLASS__, 'add_admin_menu' ] ); + // Ensures that the settings page shows up at the bottom of the menu list + add_action( 'admin_menu', [ __CLASS__, 'add_admin_menu' ], 50 ); add_action( 'admin_init', [ __CLASS__, 'helper_settings_validate_and_save' ], 100 ); add_action( 'admin_init', [ __CLASS__, 'register_settings' ] ); @@ -218,7 +219,6 @@ public static function settings_validate( $new_options ): array { * This method is called automatically/ doesn't need to be registered anywhere */ public static function helper_settings_validate_and_save(): bool { - if ( ! isset( $_POST['action'], $_POST['_wpnonce'], $_POST['option_page'], $_POST['_wp_http_referer'], $_POST['submit'] ) || ! is_admin() ) { return false; } diff --git a/modules/settings/views/settings.php b/modules/settings/views/settings.php index fc9a7213..7d23e62d 100644 --- a/modules/settings/views/settings.php +++ b/modules/settings/views/settings.php @@ -21,7 +21,13 @@

- %s', esc_html( $messages[ $message_slug ] ) ); ?> + 'success', + 'dismissible' => true, + 'additional_classes' => [ 'inline', 'notice-alt' ], + ] ); + ?>

diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index e0a49331..b3497c71 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -13,6 +13,17 @@ class OptionsUtilities { const OPTIONS_GROUP = 'vip_workflow_'; const OPTIONS_GROUP_NAME = 'vip_workflow_options'; + // Its key to centralize these here, so we can easily update them in the future + private const DEFAULT_OPTIONS = [ + 'post_types' => [ + 'post' => 'on', + 'page' => 'on', + ], + 'publish_guard' => 'on', + 'email_address' => '', + 'webhook_url' => '', + ]; + /** * Given a module name, return a set of saved module options * @@ -21,7 +32,16 @@ class OptionsUtilities { */ public static function get_module_options( string $module_slug ): object|null { $module_options_key = self::get_module_options_key( $module_slug ); - return get_option( $module_options_key, new stdClass() ); + $module_options = get_option( $module_options_key, new stdClass() ); + + // Ensure all default options are set + foreach ( self::DEFAULT_OPTIONS as $key => $value ) { + if ( ! isset( $module_options->$key ) ) { + $module_options->$key = $value; + } + } + + return $module_options; } public static function get_module_option_by_key( string $module_slug, string $key ): string|array|bool|null { From c551f761b415eee5b62fe5959a00dd722162314f Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 14:51:57 +1100 Subject: [PATCH 08/21] Add a new way to reset options after each test, and utilize it in tests --- modules/custom-status/custom-status.php | 2 -- modules/shared/php/options-utilities.php | 17 +++++++++++++++++ .../meta/test-required-metadata-id-handler.php | 9 +++++++++ .../meta/test-required-user-id-handler.php | 9 +++++++++ .../rest/test-custom-status-endpoint.php | 9 +++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 8119e47e..cc5aeaca 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -125,8 +125,6 @@ public static function register_custom_status_taxonomy(): void { * Also unregisters pending, in case the user doesn't want them. */ public static function register_custom_statuses(): void { - global $wp_post_statuses; - $custom_statuses = self::get_custom_statuses(); // Unfortunately, register_post_status() doesn't accept a diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index b3497c71..36e7b176 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -80,6 +80,23 @@ public static function update_module_options( string $module_slug, array $new_op return update_option( $module_options_key, $new_options ); } + /** + * Reset all module options, this will reset the plugin back to its default settings. + * + * It's meant for testing purposes only. + * + * @return void + * + * @access private + */ + public static function reset_all_module_options(): void { + $modules_to_delete = [ 'custom-status', 'editorial-metadata', 'settings' ]; + foreach ( $modules_to_delete as $module_slug ) { + $module_options_key = self::get_module_options_key( $module_slug ); + delete_option( $module_options_key ); + } + } + /** * Given a module name, return the options key for the module * diff --git a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php index 611e8170..bbcc7c32 100644 --- a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php @@ -9,6 +9,7 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\CustomStatus\Meta\RequiredMetadataIdHandler; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use WP_UnitTestCase; class RequiredMetadataIdHandlerTest extends WP_UnitTestCase { @@ -25,6 +26,14 @@ protected function setUp(): void { CustomStatus::setup_install(); } + // tear down after class + protected function tearDown(): void { + parent::tearDown(); + + // Reset all module options + OptionsUtilities::reset_all_module_options(); + } + public function test_remove_deleted_metadata_from_required_metadata() { $meta_id = 1; $custom_status_term = CustomStatus::add_custom_status( [ diff --git a/tests/modules/custom-status/meta/test-required-user-id-handler.php b/tests/modules/custom-status/meta/test-required-user-id-handler.php index 705cca1c..6e417bb2 100644 --- a/tests/modules/custom-status/meta/test-required-user-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-user-id-handler.php @@ -9,6 +9,7 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\CustomStatus\Meta\RequiredUserIdHandler; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use WP_UnitTestCase; class RequiredUserIdHandlerTest extends WP_UnitTestCase { @@ -25,6 +26,14 @@ protected function setUp(): void { CustomStatus::setup_install(); } + // tear down after class + protected function tearDown(): void { + parent::tearDown(); + + // Reset all module options + OptionsUtilities::reset_all_module_options(); + } + public function test_remove_deleted_user_from_required_users_no_reassigned_user() { $deleted_user_id = 1; $custom_status_term = CustomStatus::add_custom_status( [ diff --git a/tests/modules/custom-status/rest/test-custom-status-endpoint.php b/tests/modules/custom-status/rest/test-custom-status-endpoint.php index 6fc40eb8..f203de0d 100644 --- a/tests/modules/custom-status/rest/test-custom-status-endpoint.php +++ b/tests/modules/custom-status/rest/test-custom-status-endpoint.php @@ -9,6 +9,7 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\EditorialMetadata; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use WP_REST_Request; /** @@ -28,6 +29,14 @@ protected function setUp(): void { CustomStatus::setup_install(); } + // tear down after class + protected function tearDown(): void { + parent::tearDown(); + + // Reset all module options + OptionsUtilities::reset_all_module_options(); + } + public function test_create_custom_status_with_optional_fields() { $editorial_metadata_term = EditorialMetadata::insert_editorial_metadata_term( [ 'name' => 'Test Metadata 1', From 81d9411557577e81d5dc6b917642014a28df646c Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 14:56:27 +1100 Subject: [PATCH 09/21] Update the min php and wp versions --- vip-workflow.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vip-workflow.php b/vip-workflow.php index 0235691e..b1899836 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -6,8 +6,8 @@ * Author: WordPress VIP * Text Domain: vip-workflow * Version: 0.3.0 - * Requires at least: 6.2 - * Requires PHP: 8.0 + * Requires at least: 6.4 + * Requires PHP: 8.1 * License: GPL-3 * License URI: https://www.gnu.org/licenses/gpl-3.0.html * @@ -23,11 +23,11 @@ define( 'VIP_WORKFLOW_LOADED', true ); global $wp_version; -if ( version_compare( phpversion(), '8.0', '<' ) || version_compare( $wp_version, '6.2', '<' ) ) { +if ( version_compare( phpversion(), '8.1', '<' ) || version_compare( $wp_version, '6.4', '<' ) ) { add_action( 'admin_notices', function () { ?>
-

+

Date: Tue, 8 Oct 2024 15:27:07 +1100 Subject: [PATCH 10/21] Move the core hacks to their own file, re-enable the notifications tests, and shift away another helper method that's shared --- .github/workflows/php-tests.yml | 4 +- modules/custom-status/custom-status.php | 393 +---------------- .../editorial-metadata/editorial-metadata.php | 2 +- modules/settings/settings.php | 21 +- modules/shared/php/core-hacks.php | 398 ++++++++++++++++++ modules/shared/php/helper-utilities.php | 17 + modules/shared/php/util.php | 6 +- .../test-required-metadata-id-handler.php | 1 - .../meta/test-required-user-id-handler.php | 1 - .../rest/test-custom-status-endpoint.php | 1 - .../notifications/test-notifications.php | 61 +-- vip-workflow.php | 1 + 12 files changed, 462 insertions(+), 444 deletions(-) create mode 100644 modules/shared/php/core-hacks.php diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index 6bdf520b..c3287380 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -21,11 +21,11 @@ jobs: matrix: include: # Check lowest supported WP version, with the lowest supported PHP. - - php: '8.0' + - php: '8.1' wp: '6.2' allowed_failure: false # Check latest WP with the lowest supported PHP. - - php: '8.0' + - php: '8.1' wp: 'master' allowed_failure: false # Check latest WP with the highest supported PHP. diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index cc5aeaca..2d6aa097 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -1,6 +1,6 @@ post_type; } elseif ( isset( $_REQUEST['post_type'] ) ) { $post_type = sanitize_key( $_REQUEST['post_type'] ); - } elseif ( 'post.php' == $pagenow + } elseif ( 'post.php' === $pagenow && $post_id && ! empty( get_post( $post_id )->post_type ) ) { $post_type = get_post( $post_id )->post_type; - } elseif ( 'edit.php' == $pagenow && empty( $_REQUEST['post_type'] ) ) { + } elseif ( 'edit.php' === $pagenow && empty( $_REQUEST['post_type'] ) ) { $post_type = 'post'; } else { $post_type = null; @@ -277,7 +258,7 @@ public static function render_settings_view(): void { */ public static function action_admin_enqueue_scripts(): void { // Load Javascript we need to use on the configuration views - if ( Settings::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { + if ( HelperUtilities::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/custom-status/custom-status-configure.asset.php'; wp_enqueue_script( 'vip-workflow-custom-status-configure', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-configure.js', $asset_file['dependencies'], $asset_file['version'], true ); wp_enqueue_style( 'vip-workflow-custom-status-styles', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-configure.css', [ 'wp-components' ], $asset_file['version'] ); @@ -1000,370 +981,6 @@ public static function is_post_using_custom_status( int $post_id ): bool { return in_array( $post->post_type, $custom_post_types ) && in_array( $post->post_status, $status_slugs ); } - - // Hacks for custom statuses to work with core - - /** - * A new hack! hack! hack! until core better supports custom statuses` - * - * If the post_name is set, set it, otherwise keep it empty - * - * @see https://github.com/Automattic/Edit-Flow/issues/523 - * @see https://github.com/Automattic/Edit-Flow/issues/633 - * - * @param array $data The post data - * @param array $postarr The post array - * @return array $data The post data - */ - public static function maybe_keep_post_name_empty( array $data, array $postarr ): array { - $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - - // Ignore if it's not a post status and post type we support - if ( ! in_array( $data['post_status'], $status_slugs ) - || ! in_array( $data['post_type'], HelperUtilities::get_supported_post_types() ) ) { - return $data; - } - - // If the post_name was intentionally set, set the post_name - if ( ! empty( $postarr['post_name'] ) ) { - $data['post_name'] = sanitize_title( $postarr['post_name'] ); - return $data; - } - - // Otherwise, keep the post_name empty - $data['post_name'] = ''; - - return $data; - } - - /** - * A new hack! hack! hack! until core better supports custom statuses` - * - * `wp_unique_post_slug` is used to set the `post_name`. When a custom status is used, WordPress will try - * really hard to set `post_name`, and we leverage `wp_unique_post_slug` to prevent it being set - * - * @see: https://github.com/WordPress/WordPress/blob/396647666faebb109d9cd4aada7bb0c7d0fb8aca/wp-includes/post.php#L3932 - * - * @param string|null $override_slug The override slug - * @param string $slug The slug - * @param int $post_id The post ID - * @param string $post_status The post status - * @param string $post_type The post type - * @param int $post_parent The post parent - * @return string|null $override_slug The override slug - */ - public static function fix_unique_post_slug( string|null $override_slug, string $slug, int $post_id, string $post_status, string $post_type, int $post_parent ): string|null { - $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - - if ( ! in_array( $post_status, $status_slugs ) - || ! in_array( $post_type, HelperUtilities::get_supported_post_types() ) ) { - return null; - } - - $post = get_post( $post_id ); - - if ( empty( $post ) ) { - return null; - } - - if ( $post->post_name ) { - return $slug; - } - - return ''; - } - - - /** - * Another hack! hack! hack! until core better supports custom statuses - * - * The preview link for an unpublished post should always be ?p= - * - * @param string $preview_link The preview link - * @return string $preview_link The preview link - */ - public static function fix_preview_link_part_one( string $preview_link ): string { - global $pagenow; - - $post = get_post( get_the_ID() ); - - // Only modify if we're using a pre-publish status on a supported custom post type - $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - if ( ! $post - || ! is_admin() - || 'post.php' != $pagenow - || ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) - || strpos( $preview_link, 'preview_id' ) !== false - || 'sample' === $post->filter ) { - return $preview_link; - } - - return self::get_preview_link( $post ); - } - - /** - * Another hack! hack! hack! until core better supports custom statuses - * - * The preview link for an unpublished post should always be ?p= - * The code used to trigger a post preview doesn't also apply the 'preview_post_link' filter - * So we can't do a targeted filter. Instead, we can even more hackily filter get_permalink - * @see http://core.trac.wordpress.org/ticket/19378 - * - * @param string $permalink The permalink - * @param int|WP_Post $post The post object - * @param bool $sample Is this a sample permalink? - * @return string $permalink The permalink - */ - public static function fix_preview_link_part_two( string $permalink, int|WP_Post $post, bool $sample ): string { - global $pagenow; - - if ( is_int( $post ) ) { - $post = get_post( $post ); - } - - //Should we be doing anything at all? - if ( ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { - return $permalink; - } - - //Is this published? - if ( in_array( $post->post_status, self::$published_statuses ) ) { - return $permalink; - } - - //Are we overriding the permalink? Don't do anything - // phpcs:ignore:WordPress.Security.NonceVerification.Missing - if ( isset( $_POST['action'] ) && 'sample-permalink' === $_POST['action'] ) { - return $permalink; - } - - //Are we previewing the post from the normal post screen? - if ( ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) - // phpcs:ignore:WordPress.Security.NonceVerification.Missing - && ! isset( $_POST['wp-preview'] ) ) { - return $permalink; - } - - //If it's a sample permalink, not a preview - if ( $sample ) { - return $permalink; - } - - return self::get_preview_link( $post ); - } - - /** - * Another hack! hack! hack! until core better supports custom statuses - * - * The preview link for a saved unpublished post with a custom status returns a 'preview_nonce' - * in it and needs to be removed when previewing it to return a viewable preview link. - * @see https://github.com/Automattic/Edit-Flow/issues/513 - * - * @param string $preview_link The preview link - * @param WP_Post $query_args The post object - * @return string $preview_link The preview link - */ - public static function fix_preview_link_part_three( string $preview_link, WP_Post $query_args ) { - $autosave = wp_get_post_autosave( $query_args->ID, get_current_user_id() ); - if ( $autosave ) { - foreach ( array_intersect( array_keys( _wp_post_revision_fields( $query_args ) ), array_keys( _wp_post_revision_fields( $autosave ) ) ) as $field ) { - if ( normalize_whitespace( $query_args->$field ) != normalize_whitespace( $autosave->$field ) ) { - // Pass through, it's a personal preview. - return $preview_link; - } - } - } - return remove_query_arg( [ 'preview_nonce' ], $preview_link ); - } - - /** - * Fix get_sample_permalink. Previously the 'editable_slug' filter was leveraged - * to correct the sample permalink a user could edit on post.php. Since 4.4.40 - * the `get_sample_permalink` filter was added which allows greater flexibility in - * manipulating the slug. Critical for cases like editing the sample permalink on - * hierarchical post types. - * - * @param array $permalink Sample permalink - * @param int $post_id Post ID - * @param string $title Post title - * @param string $name Post name (slug) - * @param WP_Post $post Post object - * @return array $link Direct link to complete the action - */ - public static function fix_get_sample_permalink( array $permalink, int $post_id, string|null $title, string|null $name, WP_Post $post ): array { - - $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - - if ( ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { - return $permalink; - } - - remove_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); - - $new_name = ! is_null( $name ) ? $name : $post->post_name; - $new_title = ! is_null( $title ) ? $title : $post->post_title; - - $post = get_post( $post_id ); - $status_before = $post->post_status; - $post->post_status = 'draft'; - - $permalink = get_sample_permalink( $post, $title, sanitize_title( $new_name ? $new_name : $new_title, $post->ID ) ); - - $post->post_status = $status_before; - - add_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); - - return $permalink; - } - - /** - * Hack to work around post status check in get_sample_permalink_html - * - * - * The get_sample_permalink_html checks the status of the post and if it's - * a draft generates a certain permalink structure. - * We need to do the same work it's doing for custom statuses in order - * to support this link - * @see https://core.trac.wordpress.org/browser/tags/4.5.2/src/wp-admin/includes/post.php#L1296 - * - * @param array $return Sample permalink HTML markup. - * @param int $post_id Post ID. - * @param string $new_title New sample permalink title. - * @param string $new_slug New sample permalink slug. - * @param WP_Post $post Post object. - * @return array $sample_permalink_html - */ - public static function fix_get_sample_permalink_html( array $permalink, int $post_id, string|null $title, string|null $name, WP_Post $post ): array { - $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - - if ( ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { - return $permalink; - } - - remove_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); - - $post->post_status = 'draft'; - $sample_permalink_html = get_sample_permalink_html( $post, $new_title, $new_slug ); - - add_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); - - return $sample_permalink_html; - } - - - /** - * Fixes a bug where post-pagination doesn't work when previewing a post with a custom status - * @link https://github.com/Automattic/Edit-Flow/issues/192 - * - * This filter only modifies output if `is_preview()` is true - * - * Used by `wp_link_pages_link` filter - * - * @param string $link The link - * @param string $i The page number - * - * @return string $link The modified link - */ - public static function modify_preview_link_pagination_url( string $link, string $i ) { - - // Use the original $link when not in preview mode - if ( ! is_preview() ) { - return $link; - } - - // Get an array of valid custom status slugs - $custom_statuses = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - - // Apply original link filters from core `wp_link_pages()` - $r = apply_filters( 'wp_link_pages_args', [ - 'link_before' => '', - 'link_after' => '', - 'pagelink' => '%', - ]); - - // _wp_link_page() && _vw_wp_link_page() produce an opening link tag ( ) - // This is necessary to replicate core behavior: - $link = $r['link_before'] . str_replace( '%', $i, $r['pagelink'] ) . $r['link_after']; - $link = _vw_wp_link_page( $i, $custom_statuses ) . $link . ''; - - - return $link; - } - - /** - * Get the proper preview link for a post - * - * @param WP_Post $post The post object - * @return string $preview_link The preview link - */ - private static function get_preview_link( WP_Post $post ): string { - - if ( 'page' === $post->post_type ) { - $args = [ - 'page_id' => $post->ID, - ]; - } elseif ( 'post' === $post->post_type ) { - $args = [ - 'p' => $post->ID, - 'preview' => 'true', - ]; - } else { - $args = [ - 'p' => $post->ID, - 'post_type' => $post->post_type, - ]; - } - - $args['preview_id'] = $post->ID; - return add_query_arg( $args, home_url( '/' ) ); - } - - /** - * Another hack! hack! hack! until core better supports custom statuses - * - * The preview link for an unpublished post should always be ?p=, even in the list table - * @see http://core.trac.wordpress.org/ticket/19378 - */ - public static function fix_post_row_actions( array $actions, WP_Post $post ): array { - global $pagenow; - - // Only modify if we're using a pre-publish status on a supported custom post type - $status_slugs = wp_list_pluck( self::get_custom_statuses(), 'slug' ); - if ( 'edit.php' != $pagenow - || ! in_array( $post->post_status, $status_slugs ) - || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { - return $actions; - } - - // 'view' is only set if the user has permission to post - if ( empty( $actions['view'] ) ) { - return $actions; - } - - if ( 'page' === $post->post_type ) { - $args = [ - 'page_id' => $post->ID, - ]; - } elseif ( 'post' === $post->post_type ) { - $args = [ - 'p' => $post->ID, - ]; - } else { - $args = [ - 'p' => $post->ID, - 'post_type' => $post->post_type, - ]; - } - $args['preview'] = 'true'; - $preview_link = add_query_arg( $args, home_url( '/' ) ); - - /* translators: %s: post title */ - $actions['view'] = '' . __( 'Preview' ) . ''; - return $actions; - } } CustomStatus::init(); diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index 5a920a93..a6aef5e1 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -162,7 +162,7 @@ public static function render_settings_view(): void { */ public static function action_admin_enqueue_scripts(): void { // Load Javascript we need to use on the configuration views - if ( Settings::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { + if ( HelperUtilities::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/editorial-metadata/editorial-metadata-configure.asset.php'; wp_enqueue_script( 'vip-workflow-editorial-metadata-configure', VIP_WORKFLOW_URL . 'dist/modules/editorial-metadata/editorial-metadata-configure.js', $asset_file['dependencies'], $asset_file['version'], true ); wp_enqueue_style( 'vip-workflow-editorial-metadata-styles', VIP_WORKFLOW_URL . 'dist/modules/editorial-metadata/editorial-metadata-configure.css', [ 'wp-components' ], $asset_file['version'] ); diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 14136031..22ed3e4c 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -7,8 +7,8 @@ */ namespace VIPWorkflow\Modules; -use VIPWorkflow\VIP_Workflow; use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; +use VIPWorkflow\Modules\Shared\PHP\HelperUtilities; class Settings { const SETTINGS_SLUG = 'vw-settings'; @@ -41,7 +41,7 @@ public static function add_admin_menu(): void { * @access private */ public static function action_admin_enqueue_scripts(): void { - if ( self::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { + if ( HelperUtilities::is_settings_view_loaded( self::SETTINGS_SLUG ) ) { $asset_file = include VIP_WORKFLOW_ROOT . '/dist/modules/settings/settings.asset.php'; $dependencies = [ ...$asset_file['dependencies'], 'jquery' ]; wp_enqueue_script( 'vip-workflow-settings-js', VIP_WORKFLOW_URL . 'dist/modules/settings/settings.js', $dependencies, $asset_file['version'], true ); @@ -130,23 +130,6 @@ public static function helper_option_custom_post_type( $args = array() ): void { printf( '

%s

', esc_html__( 'Enable workflow custom statuses on the above post types.', 'vip-workflow' ) ); } - /** - * Whether or not the current page is our settings view. Determination is based on $pagenow, $_GET['page'], and if it's settings module or not. - * - * @return bool $is_settings_view Return true if it is - */ - public static function is_settings_view_loaded( string $slug ): bool { - global $pagenow; - - // All of the settings views are based on admin.php and a $_GET['page'] parameter - if ( 'admin.php' != $pagenow || ! isset( $_GET['page'] ) ) { - return false; - } - - // The current page better be in the array of registered settings view slugs - return $_GET['page'] === $slug; - } - /** * Cleans up the 'on' and 'off' for post types on a given module (so we don't get warnings all over) * For every post type that doesn't explicitly have the 'on' value, turn it 'off' diff --git a/modules/shared/php/core-hacks.php b/modules/shared/php/core-hacks.php new file mode 100644 index 00000000..1135c59b --- /dev/null +++ b/modules/shared/php/core-hacks.php @@ -0,0 +1,398 @@ +post_name ) { + return $slug; + } + + return ''; + } + + + /** + * Another hack! hack! hack! until core better supports custom statuses + * + * The preview link for an unpublished post should always be ?p= + * + * @param string $preview_link The preview link + * @return string $preview_link The preview link + */ + public static function fix_preview_link_part_one( string $preview_link ): string { + global $pagenow; + + $post = get_post( get_the_ID() ); + + // Only modify if we're using a pre-publish status on a supported custom post type + $status_slugs = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); + if ( ! $post + || ! is_admin() + || 'post.php' != $pagenow + || ! in_array( $post->post_status, $status_slugs ) + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) + || strpos( $preview_link, 'preview_id' ) !== false + || 'sample' === $post->filter ) { + return $preview_link; + } + + return self::get_preview_link( $post ); + } + + /** + * Another hack! hack! hack! until core better supports custom statuses + * + * The preview link for an unpublished post should always be ?p= + * The code used to trigger a post preview doesn't also apply the 'preview_post_link' filter + * So we can't do a targeted filter. Instead, we can even more hackily filter get_permalink + * @see http://core.trac.wordpress.org/ticket/19378 + * + * @param string $permalink The permalink + * @param int|WP_Post $post The post object + * @param bool $sample Is this a sample permalink? + * @return string $permalink The permalink + */ + public static function fix_preview_link_part_two( string $permalink, int|WP_Post $post, bool $sample ): string { + global $pagenow; + + if ( is_int( $post ) ) { + $post = get_post( $post ); + } + + //Should we be doing anything at all? + if ( ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { + return $permalink; + } + + //Is this published? + if ( in_array( $post->post_status, CustomStatus::PUBLISHED_STATUSES ) ) { + return $permalink; + } + + //Are we overriding the permalink? Don't do anything + // phpcs:ignore:WordPress.Security.NonceVerification.Missing + if ( isset( $_POST['action'] ) && 'sample-permalink' === $_POST['action'] ) { + return $permalink; + } + + //Are we previewing the post from the normal post screen? + if ( ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) + // phpcs:ignore:WordPress.Security.NonceVerification.Missing + && ! isset( $_POST['wp-preview'] ) ) { + return $permalink; + } + + //If it's a sample permalink, not a preview + if ( $sample ) { + return $permalink; + } + + return self::get_preview_link( $post ); + } + + /** + * Another hack! hack! hack! until core better supports custom statuses + * + * The preview link for a saved unpublished post with a custom status returns a 'preview_nonce' + * in it and needs to be removed when previewing it to return a viewable preview link. + * @see https://github.com/Automattic/Edit-Flow/issues/513 + * + * @param string $preview_link The preview link + * @param WP_Post $query_args The post object + * @return string $preview_link The preview link + */ + public static function fix_preview_link_part_three( string $preview_link, WP_Post $query_args ) { + $autosave = wp_get_post_autosave( $query_args->ID, get_current_user_id() ); + if ( $autosave ) { + foreach ( array_intersect( array_keys( _wp_post_revision_fields( $query_args ) ), array_keys( _wp_post_revision_fields( $autosave ) ) ) as $field ) { + if ( normalize_whitespace( $query_args->$field ) != normalize_whitespace( $autosave->$field ) ) { + // Pass through, it's a personal preview. + return $preview_link; + } + } + } + return remove_query_arg( [ 'preview_nonce' ], $preview_link ); + } + + /** + * Fix get_sample_permalink. Previously the 'editable_slug' filter was leveraged + * to correct the sample permalink a user could edit on post.php. Since 4.4.40 + * the `get_sample_permalink` filter was added which allows greater flexibility in + * manipulating the slug. Critical for cases like editing the sample permalink on + * hierarchical post types. + * + * @param array $permalink Sample permalink + * @param int $post_id Post ID + * @param string $title Post title + * @param string $name Post name (slug) + * @param WP_Post $post Post object + * @return array $link Direct link to complete the action + */ + public static function fix_get_sample_permalink( array $permalink, int $post_id, string|null $title, string|null $name, WP_Post $post ): array { + + $status_slugs = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); + + if ( ! in_array( $post->post_status, $status_slugs ) + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { + return $permalink; + } + + remove_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); + + $new_name = ! is_null( $name ) ? $name : $post->post_name; + $new_title = ! is_null( $title ) ? $title : $post->post_title; + + $post = get_post( $post_id ); + $status_before = $post->post_status; + $post->post_status = 'draft'; + + $permalink = get_sample_permalink( $post, $title, sanitize_title( $new_name ? $new_name : $new_title, $post->ID ) ); + + $post->post_status = $status_before; + + add_filter( 'get_sample_permalink', [ __CLASS__, 'fix_get_sample_permalink' ], 10, 5 ); + + return $permalink; + } + + /** + * Hack to work around post status check in get_sample_permalink_html + * + * + * The get_sample_permalink_html checks the status of the post and if it's + * a draft generates a certain permalink structure. + * We need to do the same work it's doing for custom statuses in order + * to support this link + * @see https://core.trac.wordpress.org/browser/tags/4.5.2/src/wp-admin/includes/post.php#L1296 + * + * @param array $return Sample permalink HTML markup. + * @param int $post_id Post ID. + * @param string $new_title New sample permalink title. + * @param string $new_slug New sample permalink slug. + * @param WP_Post $post Post object. + * @return array $sample_permalink_html + */ + public static function fix_get_sample_permalink_html( array $permalink, int $post_id, string|null $new_title, string|null $new_slug, WP_Post $post ): array { + $status_slugs = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); + + if ( ! in_array( $post->post_status, $status_slugs ) + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { + return $permalink; + } + + remove_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); + + $post->post_status = 'draft'; + $sample_permalink_html = get_sample_permalink_html( $post, $new_title, $new_slug ); + + add_filter( 'get_sample_permalink_html', [ __CLASS__, 'fix_get_sample_permalink_html' ], 10, 5 ); + + return $sample_permalink_html; + } + + + /** + * Fixes a bug where post-pagination doesn't work when previewing a post with a custom status + * @link https://github.com/Automattic/Edit-Flow/issues/192 + * + * This filter only modifies output if `is_preview()` is true + * + * Used by `wp_link_pages_link` filter + * + * @param string $link The link + * @param string $i The page number + * + * @return string $link The modified link + */ + public static function modify_preview_link_pagination_url( string $link, string $i ) { + + // Use the original $link when not in preview mode + if ( ! is_preview() ) { + return $link; + } + + // Get an array of valid custom status slugs + $custom_statuses = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); + + // Apply original link filters from core `wp_link_pages()` + $r = apply_filters( 'wp_link_pages_args', [ + 'link_before' => '', + 'link_after' => '', + 'pagelink' => '%', + ]); + + // _wp_link_page() && _vw_wp_link_page() produce an opening link tag ( ) + // This is necessary to replicate core behavior: + $link = $r['link_before'] . str_replace( '%', $i, $r['pagelink'] ) . $r['link_after']; + $link = _vw_wp_link_page( $i, $custom_statuses ) . $link . ''; + + + return $link; + } + + /** + * Get the proper preview link for a post + * + * @param WP_Post $post The post object + * @return string $preview_link The preview link + */ + private static function get_preview_link( WP_Post $post ): string { + + if ( 'page' === $post->post_type ) { + $args = [ + 'page_id' => $post->ID, + ]; + } elseif ( 'post' === $post->post_type ) { + $args = [ + 'p' => $post->ID, + 'preview' => 'true', + ]; + } else { + $args = [ + 'p' => $post->ID, + 'post_type' => $post->post_type, + ]; + } + + $args['preview_id'] = $post->ID; + return add_query_arg( $args, home_url( '/' ) ); + } + + /** + * Another hack! hack! hack! until core better supports custom statuses + * + * The preview link for an unpublished post should always be ?p=, even in the list table + * @see http://core.trac.wordpress.org/ticket/19378 + */ + public static function fix_post_row_actions( array $actions, WP_Post $post ): array { + global $pagenow; + + // Only modify if we're using a pre-publish status on a supported custom post type + $status_slugs = wp_list_pluck( CustomStatus::get_custom_statuses(), 'slug' ); + if ( 'edit.php' != $pagenow + || ! in_array( $post->post_status, $status_slugs ) + || ! in_array( $post->post_type, HelperUtilities::get_supported_post_types() ) ) { + return $actions; + } + + // 'view' is only set if the user has permission to post + if ( empty( $actions['view'] ) ) { + return $actions; + } + + if ( 'page' === $post->post_type ) { + $args = [ + 'page_id' => $post->ID, + ]; + } elseif ( 'post' === $post->post_type ) { + $args = [ + 'p' => $post->ID, + ]; + } else { + $args = [ + 'p' => $post->ID, + 'post_type' => $post->post_type, + ]; + } + $args['preview'] = 'true'; + $preview_link = add_query_arg( $args, home_url( '/' ) ); + + /* translators: %s: post title */ + $actions['view'] = '' . __( 'Preview' ) . ''; + return $actions; + } +} + +CoreHacks::init(); diff --git a/modules/shared/php/helper-utilities.php b/modules/shared/php/helper-utilities.php index b999f4ab..cd43eec4 100644 --- a/modules/shared/php/helper-utilities.php +++ b/modules/shared/php/helper-utilities.php @@ -29,4 +29,21 @@ public static function get_supported_post_types(): array { return $post_types; } + + /** + * Whether or not the current page is our settings view. Determination is based on $pagenow, $_GET['page'], and if it's settings module or not. + * + * @return bool $is_settings_view Return true if it is + */ + public static function is_settings_view_loaded( string $slug ): bool { + global $pagenow; + + // All of the settings views are based on admin.php and a $_GET['page'] parameter + if ( 'admin.php' != $pagenow || ! isset( $_GET['page'] ) ) { + return false; + } + + // The current page better be in the array of registered settings view slugs + return $_GET['page'] === $slug; + } } diff --git a/modules/shared/php/util.php b/modules/shared/php/util.php index c886f262..c624caff 100644 --- a/modules/shared/php/util.php +++ b/modules/shared/php/util.php @@ -40,14 +40,14 @@ function _vw_wp_link_page( $i, $custom_statuses ) { $post = get_post(); $query_args = array(); - if ( 1 == $i ) { + if ( 1 === $i ) { $url = get_permalink(); // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found } else { // Check for all custom post statuses, not just draft & pending - if ( '' == get_option( 'permalink_structure' ) || in_array( $post->post_status, array_merge( $custom_statuses, array( 'pending' ) ) ) ) { + if ( '' === get_option( 'permalink_structure' ) || in_array( $post->post_status, array_merge( $custom_statuses, array( 'pending' ) ) ) ) { $url = add_query_arg( 'page', $i, get_permalink() ); - } elseif ( 'page' == get_option( 'show_on_front' ) && get_option( 'page_on_front' ) == $post->ID ) { + } elseif ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) === $post->ID ) { $url = trailingslashit( get_permalink() ) . user_trailingslashit( "$wp_rewrite->pagination_base/" . $i, 'single_paged' ); } else { $url = trailingslashit( get_permalink() ) . user_trailingslashit( $i, 'single_paged' ); diff --git a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php index bbcc7c32..1cdc26a4 100644 --- a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php @@ -26,7 +26,6 @@ protected function setUp(): void { CustomStatus::setup_install(); } - // tear down after class protected function tearDown(): void { parent::tearDown(); diff --git a/tests/modules/custom-status/meta/test-required-user-id-handler.php b/tests/modules/custom-status/meta/test-required-user-id-handler.php index 6e417bb2..8abb55ce 100644 --- a/tests/modules/custom-status/meta/test-required-user-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-user-id-handler.php @@ -26,7 +26,6 @@ protected function setUp(): void { CustomStatus::setup_install(); } - // tear down after class protected function tearDown(): void { parent::tearDown(); diff --git a/tests/modules/custom-status/rest/test-custom-status-endpoint.php b/tests/modules/custom-status/rest/test-custom-status-endpoint.php index f203de0d..ab5a3bcc 100644 --- a/tests/modules/custom-status/rest/test-custom-status-endpoint.php +++ b/tests/modules/custom-status/rest/test-custom-status-endpoint.php @@ -29,7 +29,6 @@ protected function setUp(): void { CustomStatus::setup_install(); } - // tear down after class protected function tearDown(): void { parent::tearDown(); diff --git a/tests/modules/notifications/test-notifications.php b/tests/modules/notifications/test-notifications.php index 5aa985a0..24cc91ff 100644 --- a/tests/modules/notifications/test-notifications.php +++ b/tests/modules/notifications/test-notifications.php @@ -9,6 +9,8 @@ namespace VIPWorkflow\Tests; use VIPWorkflow\Modules\Notifications; +use VIPWorkflow\Modules\Settings; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use WP_Error; use WP_UnitTestCase; @@ -18,6 +20,9 @@ protected function tearDown(): void { parent::tearDown(); reset_phpmailer_instance(); + + // Reset all module options + OptionsUtilities::reset_all_module_options(); } public function test_validate_get_notification_footer() { @@ -43,41 +48,41 @@ public function test_send_emails() { $this->assertDiscardWhitespace( $body, $email->body ); } - // public function test_send_to_webhook_happy_path() { - // // Hook in and return a known response - // add_filter( 'pre_http_request', function () { - // return array( - // 'headers' => array(), - // 'cookies' => array(), - // 'filename' => null, - // 'response' => 200, - // 'status_code' => 200, - // 'success' => 1, - // 'body' => 'All Done', - // ); - // }, 10, 3 ); + public function test_send_to_webhook_happy_path() { + // Hook in and return a known response + add_filter( 'pre_http_request', function () { + return array( + 'headers' => array(), + 'cookies' => array(), + 'filename' => null, + 'response' => 200, + 'status_code' => 200, + 'success' => 1, + 'body' => 'All Done', + ); + }, 10, 3 ); - // VIP_Workflow::instance()->settings->module->options->webhook_url = 'https://webhook.site/this-url-doesnt-exist'; + OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', 'https://webhook.site/this-url-doesnt-exist' ); - // $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); + $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); - // $this->assertTrue( $response ); + $this->assertTrue( $response ); - // VIP_Workflow::instance()->settings->module->options->webhook_url = ''; - // } + OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', '' ); + } - // public function test_send_to_webhook_error_path() { - // // Hook in and return a known response - // add_filter( 'pre_http_request', function () { - // return new WP_Error( 'http_request_failed', 'Error Message' ); - // }, 10, 3 ); + public function test_send_to_webhook_error_path() { + // Hook in and return a known response + add_filter( 'pre_http_request', function () { + return new WP_Error( 'http_request_failed', 'Error Message' ); + }, 10, 3 ); - // VIP_Workflow::instance()->settings->module->options->webhook_url = 'https://webhook.site/this-url-doesnt-exist'; + OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', 'https://webhook.site/this-url-doesnt-exist' ); - // $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); + $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); - // $this->assertFalse( $response ); + $this->assertFalse( $response ); - // VIP_Workflow::instance()->settings->module->options->webhook_url = ''; - // } + OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', '' ); + } } diff --git a/vip-workflow.php b/vip-workflow.php index b1899836..16388acb 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -59,6 +59,7 @@ require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/options-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/meta-cleanup-utilities.php'; require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/util.php'; +require_once VIP_WORKFLOW_ROOT . '/modules/shared/php/core-hacks.php'; // Modules require_once VIP_WORKFLOW_ROOT . '/modules/settings/settings.php'; From d44d89c3935ef52bdd5c859e03e8496172e0c0e5 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 15:29:03 +1100 Subject: [PATCH 11/21] Fix the cs problems --- modules/shared/php/core-hacks.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/shared/php/core-hacks.php b/modules/shared/php/core-hacks.php index 1135c59b..59199d99 100644 --- a/modules/shared/php/core-hacks.php +++ b/modules/shared/php/core-hacks.php @@ -7,11 +7,10 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\Shared\PHP\HelperUtilities; +use WP_Post; use function VIPWorkflow\Modules\Shared\PHP\_vw_wp_link_page; -use WP_Post; - class CoreHacks { public static function init(): void { From b9387f73cd289c9dcdce9bcab6114bdced075c7e Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 15:29:58 +1100 Subject: [PATCH 12/21] Up the WP version --- .github/workflows/php-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index c3287380..ec7a0265 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -22,7 +22,7 @@ jobs: include: # Check lowest supported WP version, with the lowest supported PHP. - php: '8.1' - wp: '6.2' + wp: '6.4' allowed_failure: false # Check latest WP with the lowest supported PHP. - php: '8.1' From d55f05030db0dc52cfb8b2dc11992f38844daa38 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 15:52:26 +1100 Subject: [PATCH 13/21] Set the php version correctly --- composer.json | 2 +- composer.lock | 86 +++++++++++++++++++++++++-------------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/composer.json b/composer.json index 93cc8fd0..29466a9e 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ }, "require": { "composer/installers": "~1.0", - "php": ">=8.0" + "php": ">=8.1" }, "require-dev": { "phpcompatibility/phpcompatibility-wp": "2.1.4", diff --git a/composer.lock b/composer.lock index 5a028bde..45fbf62d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4d7dc4ab72013b562eae5e1c4a7862e3", + "content-hash": "d63fb105d5efd1d85f8bb3c3539fa238", "packages": [ { "name": "composer/installers", @@ -293,30 +293,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -343,7 +343,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -359,7 +359,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "myclabs/deep-copy", @@ -423,16 +423,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -475,9 +475,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "phar-io/manifest", @@ -954,35 +954,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -991,7 +991,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -1020,7 +1020,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -1028,7 +1028,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2397,16 +2397,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.2", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -2473,7 +2473,7 @@ "type": "open_collective" } ], - "time": "2024-07-21T23:26:44+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "theseer/tokenizer", @@ -2658,7 +2658,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.0" + "php": ">=8.1" }, "platform-dev": [], "plugin-api-version": "2.6.0" From 4335679d349abf8e4fad3a59f905fe4152bb6f0d Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 15:59:06 +1100 Subject: [PATCH 14/21] Add the missing = --- modules/settings/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 22ed3e4c..15d73cf9 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -141,7 +141,7 @@ private static function clean_post_type_options( $module_post_types = array() ): $normalized_post_type_options = array(); $all_post_types = array_keys( self::get_all_post_types() ); foreach ( $all_post_types as $post_type ) { - if ( ( isset( $module_post_types[ $post_type ] ) && 'on' == $module_post_types[ $post_type ] ) ) { + if ( ( isset( $module_post_types[ $post_type ] ) && 'on' === $module_post_types[ $post_type ] ) ) { $normalized_post_type_options[ $post_type ] = 'on'; } else { $normalized_post_type_options[ $post_type ] = 'off'; From d078790cfe7d4134904fccbbb5e3c02d2c70f4fe Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 15:59:34 +1100 Subject: [PATCH 15/21] More missing == --- modules/notifications/notifications.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/notifications/notifications.php b/modules/notifications/notifications.php index 0cf802bf..3cee4d32 100644 --- a/modules/notifications/notifications.php +++ b/modules/notifications/notifications.php @@ -91,33 +91,33 @@ public static function notification_status_change( string $new_status, string $o // Email subject and first line of body // Set message subjects according to what action is being taken on the Post - if ( 'new' == $old_status || 'auto-draft' == $old_status ) { + if ( 'new' === $old_status || 'auto-draft' === $old_status ) { $old_status_friendly_name = 'New'; /* translators: 1: site name, 2: post type, 3. post title */ $subject = sprintf( __( '[%1$s] New %2$s Created: "%3$s"', 'vip-workflow' ), $blogname, $subject_post_type, $post_title ); /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */ $body .= sprintf( __( 'A new %1$s (#%2$s "%3$s") was created by %4$s %5$s.', 'vip-workflow' ), $post_type, $post_id, $post_title, $current_user->display_name, $current_user->user_email ) . "\r\n"; - } elseif ( 'trash' == $new_status ) { + } elseif ( 'trash' === $new_status ) { /* translators: 1: site name, 2: post type, 3. post title */ $subject = sprintf( __( '[%1$s] %2$s Trashed: "%3$s"', 'vip-workflow' ), $blogname, $subject_post_type, $post_title ); /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */ $body .= sprintf( __( '%1$s #%2$s "%3$s" was moved to the trash by %4$s %5$s.', 'vip-workflow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n"; - } elseif ( 'trash' == $old_status ) { + } elseif ( 'trash' === $old_status ) { /* translators: 1: site name, 2: post type, 3. post title */ $subject = sprintf( __( '[%1$s] %2$s Restored (from Trash): "%3$s"', 'vip-workflow' ), $blogname, $subject_post_type, $post_title ); /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */ $body .= sprintf( __( '%1$s #%2$s "%3$s" was restored from trash by %4$s %5$s.', 'vip-workflow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n"; - } elseif ( 'future' == $new_status ) { + } elseif ( 'future' === $new_status ) { /* translators: 1: site name, 2: post type, 3. post title */ $subject = sprintf( __( '[%1$s] %2$s Scheduled: "%3$s"' ), $blogname, $subject_post_type, $post_title ); /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email 6. scheduled date */ $body .= sprintf( __( '%1$s #%2$s "%3$s" was scheduled by %4$s %5$s. It will be published on %6$s' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email, self::get_scheduled_datetime( $post ) ) . "\r\n"; - } elseif ( 'publish' == $new_status ) { + } elseif ( 'publish' === $new_status ) { /* translators: 1: site name, 2: post type, 3. post title */ $subject = sprintf( __( '[%1$s] %2$s Published: "%3$s"', 'vip-workflow' ), $blogname, $subject_post_type, $post_title ); /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */ $body .= sprintf( __( '%1$s #%2$s "%3$s" was published by %4$s %5$s.', 'vip-workflow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n"; - } elseif ( 'publish' == $old_status ) { + } elseif ( 'publish' === $old_status ) { /* translators: 1: site name, 2: post type, 3. post title */ $subject = sprintf( __( '[%1$s] %2$s Unpublished: "%3$s"', 'vip-workflow' ), $blogname, $subject_post_type, $post_title ); /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */ From eae38374e295d1d2fac913fcae875c1463eac34f Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 16:00:35 +1100 Subject: [PATCH 16/21] Up the workflow version of the plugin --- vip-workflow.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vip-workflow.php b/vip-workflow.php index 16388acb..5be07caa 100644 --- a/vip-workflow.php +++ b/vip-workflow.php @@ -5,7 +5,7 @@ * Description: Adding additional editorial workflow capabilities to WordPress. * Author: WordPress VIP * Text Domain: vip-workflow - * Version: 0.3.0 + * Version: 0.4.0 * Requires at least: 6.4 * Requires PHP: 8.1 * License: GPL-3 @@ -35,15 +35,16 @@ } // Define contants -define( 'VIP_WORKFLOW_VERSION', '0.3.0' ); +define( 'VIP_WORKFLOW_VERSION', '0.4.0' ); define( 'VIP_WORKFLOW_ROOT', __DIR__ ); define( 'VIP_WORKFLOW_URL', plugins_url( '/', __FILE__ ) ); define( 'VIP_WORKFLOW_SETTINGS_PAGE', add_query_arg( 'page', 'vw-settings', get_admin_url( null, 'admin.php' ) ) ); define( 'VIP_WORKFLOW_REST_NAMESPACE', 'vip-workflow/v1' ); -// Set the version for the plugin +// Set the version for the plugin. // It's not used for anything, which is why it's here. +// This should not rely on any other code in the plugin. add_action( 'admin_init', function () { $previous_version = get_option( 'vip_workflow_version' ); if ( $previous_version && version_compare( $previous_version, VIP_WORKFLOW_VERSION, '<' ) ) { From 47479b4d1511b0ce9823a0a35109488b13fb4095 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 8 Oct 2024 16:19:38 +1100 Subject: [PATCH 17/21] Up the package version info --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52ef747c..614232c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vip-workflow", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vip-workflow", - "version": "0.3.0", + "version": "0.4.0", "license": "GPL-3.0-or-later", "devDependencies": { "@automattic/eslint-plugin-wpvip": "^0.13.0", diff --git a/package.json b/package.json index 19eabacc..383a652f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vip-workflow", - "version": "0.3.0", + "version": "0.4.0", "description": "VIP Workflow", "scripts": { "build": "webpack --mode production", From c2da69ac5736225d35fc21f3b9de7b2d80d1700b Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 9 Oct 2024 14:14:01 +1100 Subject: [PATCH 18/21] Re-order the taxonomy registration process --- modules/custom-status/custom-status.php | 4 ++-- modules/editorial-metadata/editorial-metadata.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 90c83934..b6c032ad 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -41,8 +41,8 @@ class CustomStatus { private static $custom_statuses_cache = []; public static function init(): void { - // Register the taxonomy we use with WordPress core - add_action( 'init', [ __CLASS__, 'register_custom_status_taxonomy' ] ); + // Register the taxonomy we use with WordPress core, and ensure it's registered after editorial metadata + add_action( 'init', [ __CLASS__, 'register_custom_status_taxonomy' ], 50 ); // Register the custom statuses in core add_action( 'init', [ __CLASS__, 'register_custom_statuses' ] ); diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index a6aef5e1..c49e3533 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -30,8 +30,8 @@ class EditorialMetadata { private static $editorial_metadata_terms_cache = []; public static function init(): void { - // Register the taxonomy we use for Editorial Metadata with WordPress core - add_action( 'init', [ __CLASS__, 'register_editorial_metadata_taxonomy' ] ); + // Register the taxonomy we use for Editorial Metadata with WordPress core, and ensure its registered before custom status + add_action( 'init', [ __CLASS__, 'register_editorial_metadata_taxonomy' ], 10 ); // Register the post meta for each editorial metadata term add_action( 'init', [ __CLASS__, 'register_editorial_metadata_terms_as_post_meta' ] ); From 5478576998cb5fc98e27cd20c96dc6171e706b18 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 9 Oct 2024 15:24:40 +1100 Subject: [PATCH 19/21] Redo the way default options are handled --- modules/settings/settings.php | 14 ++++++- modules/shared/php/options-utilities.php | 42 ++++++++----------- .../test-required-metadata-id-handler.php | 12 ++---- .../meta/test-required-user-id-handler.php | 12 ++---- .../rest/test-custom-status-endpoint.php | 12 ++---- .../notifications/test-notifications.php | 8 +--- 6 files changed, 45 insertions(+), 55 deletions(-) diff --git a/modules/settings/settings.php b/modules/settings/settings.php index 7882ccee..fb01c961 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -13,6 +13,17 @@ class Settings { const SETTINGS_SLUG = 'vw-settings'; + // Its key to centralize these, so we can easily update them in the future + const DEFAULT_SETTINGS_OPTIONS = [ + 'post_types' => [ + 'post' => 'on', + 'page' => 'on', + ], + 'publish_guard' => 'on', + 'email_address' => '', + 'webhook_url' => '', + ]; + /** * Initialize the rest of the stuff in the class if the module is active */ @@ -223,7 +234,8 @@ public static function helper_settings_validate_and_save(): bool { $new_options = self::settings_validate( $new_options ); - OptionsUtilities::update_module_options( self::SETTINGS_SLUG, $new_options ); + // Blend the new options with the old options, including any new options that may have been added + OptionsUtilities::update_module_options( $new_options ); // Redirect back to the settings page that was submitted without any previous messages $goback = add_query_arg( 'message', 'settings-updated', remove_query_arg( [ 'message' ], wp_get_referer() ) ); diff --git a/modules/shared/php/options-utilities.php b/modules/shared/php/options-utilities.php index cdfb7e1e..0ceb32ce 100644 --- a/modules/shared/php/options-utilities.php +++ b/modules/shared/php/options-utilities.php @@ -14,31 +14,23 @@ class OptionsUtilities { const OPTIONS_GROUP = 'vip_workflow_'; const OPTIONS_GROUP_NAME = 'vip_workflow_options'; - // Its key to centralize these here, so we can easily update them in the future - private const DEFAULT_OPTIONS = [ - 'post_types' => [ - 'post' => 'on', - 'page' => 'on', - ], - 'publish_guard' => 'on', - 'email_address' => '', - 'webhook_url' => '', - ]; - /** * Given a module name, return a set of saved module options * * @param string $module_slug The slug used for this module + * @param array $default_options The default options for the module * @return object The set of saved module options for this module, or an empty stdClass if none are found */ - public static function get_module_options( string $module_slug ): object|null { + public static function get_module_options( string $module_slug, array $default_options = [] ): object|null { $module_options_key = self::get_module_options_key( $module_slug ); $module_options = get_option( $module_options_key, new stdClass() ); - // Ensure all default options are set - foreach ( self::DEFAULT_OPTIONS as $key => $value ) { - if ( ! isset( $module_options->$key ) ) { - $module_options->$key = $value; + if ( [] !== $module_options ) { + // Ensure all default options are set + foreach ( $default_options as $key => $value ) { + if ( ! isset( $module_options->$key ) ) { + $module_options->$key = $value; + } } } @@ -54,20 +46,23 @@ public static function get_module_options( string $module_slug ): object|null { * @return string|array|boolean|null The value of the key, or null if it doesn't exist */ public static function get_options_by_key( string $key ): string|array|bool|null { - $module_options = self::get_module_options( Settings::SETTINGS_SLUG ); + $module_options = self::get_module_options( Settings::SETTINGS_SLUG, Settings::DEFAULT_SETTINGS_OPTIONS ); return $module_options->$key; } /** - * Update a module option, using the module's name and the key + * Update a module option, using the module's name and the key. + * + * Note: This method is used to update a single key in the module options, so it will override the entire options object. * * @param string $module_slug The slug used for this module * @param string $key The option key * @param string $value The option value + * @param array $default_options The default options for the module * @return bool True if the option was updated, false otherwise. */ - public static function update_module_option_key( string $module_slug, string $key, string $value ): bool { - $module_options = self::get_module_options( $module_slug ); + public static function update_module_option_key( string $module_slug, string $key, string $value, array $default_options = [] ): bool { + $module_options = self::get_module_options( $module_slug, $default_options ); $module_options->$key = $value; $module_options_key = self::get_module_options_key( $module_slug ); @@ -77,13 +72,12 @@ public static function update_module_option_key( string $module_slug, string $ke /** * Update a module options, using the module's name * - * @param string $module_slug The slug used for this module * @param object $new_options The new options to save * @return bool True if the options were updated, false otherwise. */ - public static function update_module_options( string $module_slug, array $new_options ): bool { - $module_options_key = self::get_module_options_key( $module_slug ); - $old_options = self::get_module_options( $module_slug ); + public static function update_module_options( array $new_options ): bool { + $module_options_key = self::get_module_options_key( Settings::SETTINGS_SLUG ); + $old_options = self::get_module_options( Settings::SETTINGS_SLUG, Settings::DEFAULT_SETTINGS_OPTIONS ); $new_options = (object) array_merge( (array) $old_options, $new_options ); return update_option( $module_options_key, $new_options ); diff --git a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php index 1cdc26a4..b5df9378 100644 --- a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php @@ -15,24 +15,20 @@ class RequiredMetadataIdHandlerTest extends WP_UnitTestCase { /** - * Before each test, ensure default custom statuses are available. + * Before each test, ensure default custom statuses are available and reset all module options. */ protected function setUp(): void { parent::setUp(); + // Reset all module options + OptionsUtilities::reset_all_module_options(); + // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes // a status and it's the only status existing, which can cause errors due to status restrictions. CustomStatus::setup_install(); } - protected function tearDown(): void { - parent::tearDown(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); - } - public function test_remove_deleted_metadata_from_required_metadata() { $meta_id = 1; $custom_status_term = CustomStatus::add_custom_status( [ diff --git a/tests/modules/custom-status/meta/test-required-user-id-handler.php b/tests/modules/custom-status/meta/test-required-user-id-handler.php index 8abb55ce..b8602358 100644 --- a/tests/modules/custom-status/meta/test-required-user-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-user-id-handler.php @@ -15,24 +15,20 @@ class RequiredUserIdHandlerTest extends WP_UnitTestCase { /** - * Before each test, ensure default custom statuses are available. + * Before each test, ensure default custom statuses are available and reset all module options. */ protected function setUp(): void { parent::setUp(); + // Reset all module options + OptionsUtilities::reset_all_module_options(); + // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes // a status and it's the only status existing, which can cause errors due to status restrictions. CustomStatus::setup_install(); } - protected function tearDown(): void { - parent::tearDown(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); - } - public function test_remove_deleted_user_from_required_users_no_reassigned_user() { $deleted_user_id = 1; $custom_status_term = CustomStatus::add_custom_status( [ diff --git a/tests/modules/custom-status/rest/test-custom-status-endpoint.php b/tests/modules/custom-status/rest/test-custom-status-endpoint.php index ab5a3bcc..2211b545 100644 --- a/tests/modules/custom-status/rest/test-custom-status-endpoint.php +++ b/tests/modules/custom-status/rest/test-custom-status-endpoint.php @@ -18,24 +18,20 @@ class CustomStatusRestApiTest extends RestTestCase { /** - * Before each test, ensure default custom statuses are available. + * Before each test, ensure default custom statuses are available and reset all module options. */ protected function setUp(): void { parent::setUp(); + // Reset all module options + OptionsUtilities::reset_all_module_options(); + // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes // a status and it's the only status existing, which can cause errors due to status restrictions. CustomStatus::setup_install(); } - protected function tearDown(): void { - parent::tearDown(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); - } - public function test_create_custom_status_with_optional_fields() { $editorial_metadata_term = EditorialMetadata::insert_editorial_metadata_term( [ 'name' => 'Test Metadata 1', diff --git a/tests/modules/notifications/test-notifications.php b/tests/modules/notifications/test-notifications.php index df76c763..236b41d4 100644 --- a/tests/modules/notifications/test-notifications.php +++ b/tests/modules/notifications/test-notifications.php @@ -62,13 +62,11 @@ public function test_send_to_webhook_happy_path() { ]; }, 10, 3 ); - OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', 'https://webhook.site/this-url-doesnt-exist' ); + OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', 'https://webhook.site/this-url-doesnt-exist', Settings::DEFAULT_SETTINGS_OPTIONS ); $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); $this->assertTrue( $response ); - - OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', '' ); } public function test_send_to_webhook_error_path() { @@ -77,12 +75,10 @@ public function test_send_to_webhook_error_path() { return new WP_Error( 'http_request_failed', 'Error Message' ); }, 10, 3 ); - OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', 'https://webhook.site/this-url-doesnt-exist' ); + OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', 'https://webhook.site/this-url-doesnt-exist', Settings::DEFAULT_SETTINGS_OPTIONS ); $response = Notifications::send_to_webhook( 'Test Message', 'status-change', '2024-09-19 00:26:50' ); $this->assertFalse( $response ); - - OptionsUtilities::update_module_option_key( Settings::SETTINGS_SLUG, 'webhook_url', '' ); } } From f6a362e4821a882ade38fdc2de638b75538532e7 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 9 Oct 2024 16:13:47 +1100 Subject: [PATCH 20/21] Remove the taxonomy registration priority and add an optimized version of the editorial metadata lookup --- modules/custom-status/custom-status.php | 36 ++++++++++++++++--- .../editorial-metadata/editorial-metadata.php | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index b6c032ad..a1734ba5 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -42,7 +42,7 @@ class CustomStatus { public static function init(): void { // Register the taxonomy we use with WordPress core, and ensure it's registered after editorial metadata - add_action( 'init', [ __CLASS__, 'register_custom_status_taxonomy' ], 50 ); + add_action( 'init', [ __CLASS__, 'register_custom_status_taxonomy' ] ); // Register the custom statuses in core add_action( 'init', [ __CLASS__, 'register_custom_statuses' ] ); @@ -202,7 +202,7 @@ public static function action_admin_enqueue_scripts(): void { wp_enqueue_style( 'vip-workflow-custom-status-styles', VIP_WORKFLOW_URL . 'dist/modules/custom-status/custom-status-configure.css', [ 'wp-components' ], $asset_file['version'] ); wp_localize_script( 'vip-workflow-custom-status-configure', 'VW_CUSTOM_STATUS_CONFIGURE', [ - 'custom_statuses' => self::get_custom_statuses(), + 'custom_statuses' => self::modify_custom_statuses_with_editorial_metadata(), 'editorial_metadatas' => EditorialMetadata::get_editorial_metadata_terms(), 'url_edit_status' => CustomStatusEndpoint::get_crud_url(), 'url_reorder_status' => CustomStatusEndpoint::get_reorder_url(), @@ -227,7 +227,6 @@ public static function action_admin_enqueue_scripts(): void { } } - /** * Enqueue resources that we need in the admin settings page * @@ -242,11 +241,40 @@ public static function load_scripts_for_block_editor(): void { wp_localize_script( 'vip-workflow-block-custom-status-script', 'VW_CUSTOM_STATUSES', [ 'current_user_id' => get_current_user_id(), 'is_publish_guard_enabled' => $publish_guard_enabled, - 'status_terms' => self::get_custom_statuses(), + 'status_terms' => self::modify_custom_statuses_with_editorial_metadata(), 'supported_post_types' => HelperUtilities::get_supported_post_types(), ] ); } + /** + * Modify the custom statuses to include the editorial metadatas for UI purposes. + * + * This isn't done anywhere else due to the taxonomies being registered at different times. + * In addition, registering the taxonomies in the wrong order can cause the manage posts page to break + * as well as the default status for a post itself. + * + * @return array $custom_statuses The custom statuses with the editorial metadatas included + */ + private static function modify_custom_statuses_with_editorial_metadata(): array { + // map the editorial metadatas to their respective term_id so the term_id can be used to get the full object quickly. + $editorial_metadatas = EditorialMetadata::get_editorial_metadata_terms(); + $editorial_metadatas = array_combine( array_column( $editorial_metadatas, 'term_id' ), $editorial_metadatas ); + + $custom_statuses = self::get_custom_statuses(); + + // Add the required editorial metadata to the custom statuses for UI purposes + foreach ( $custom_statuses as $status ) { + $required_metadata_ids = $status->meta[ self::METADATA_REQ_EDITORIAL_IDS_KEY ] ?? []; + $required_metadatas = []; + foreach ( $required_metadata_ids as $metadata_id ) { + $required_metadatas[] = $editorial_metadatas[ $metadata_id ]; + } + $status->meta[ self::METADATA_REQ_EDITORIALS_KEY ] = $required_metadatas; + } + + return $custom_statuses; + } + /** * Enqueue resources that we need in the block editor * diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index c49e3533..03ce31f4 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -31,7 +31,7 @@ class EditorialMetadata { public static function init(): void { // Register the taxonomy we use for Editorial Metadata with WordPress core, and ensure its registered before custom status - add_action( 'init', [ __CLASS__, 'register_editorial_metadata_taxonomy' ], 10 ); + add_action( 'init', [ __CLASS__, 'register_editorial_metadata_taxonomy' ] ); // Register the post meta for each editorial metadata term add_action( 'init', [ __CLASS__, 'register_editorial_metadata_terms_as_post_meta' ] ); From c69cb671d73a007732ab97c25b6c7edd5b4438eb Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Wed, 9 Oct 2024 11:07:33 -0600 Subject: [PATCH 21/21] Change all tests to extend WP_UnitTestCase, use built-in factory methods, remove unnecessary per-test cleanup --- composer.json | 5 +- composer.lock | 71 ++++++++++++++++--- tests/bootstrap.php | 13 ++-- .../test-required-metadata-id-handler.php | 32 ++------- .../meta/test-required-user-id-handler.php | 44 +++--------- .../rest/test-custom-status-endpoint.php | 46 ++++-------- .../test-custom-status-transitions.php | 28 ++++---- .../test-editorial-metadata-endpoint.php | 4 -- .../notifications/test-notifications.php | 12 ++-- tests/rest-test-case.php | 9 +-- tests/workflow-test-case.php | 55 +++----------- 11 files changed, 128 insertions(+), 191 deletions(-) diff --git a/composer.json b/composer.json index 29466a9e..cb119f50 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,10 @@ "require-dev": { "phpcompatibility/phpcompatibility-wp": "2.1.4", "phpunit/phpunit": "9.6.13", - "yoast/phpunit-polyfills": "2.0.0", + "yoast/phpunit-polyfills": "^3.0", "automattic/vipwpcs": "^3.0", - "sirbrillig/phpcs-variable-analysis": "^2.11" + "sirbrillig/phpcs-variable-analysis": "^2.11", + "wp-phpunit/wp-phpunit": "^6.6" }, "scripts": { "cs": "@php ./vendor/bin/phpcs -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*\"", diff --git a/composer.lock b/composer.lock index 45fbf62d..130de7a2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d63fb105d5efd1d85f8bb3c3539fa238", + "content-hash": "5f0f3371efc8cc5c94fa51e79d154d57", "packages": [ { "name": "composer/installers", @@ -2591,31 +2591,81 @@ ], "time": "2024-03-25T16:39:00+00:00" }, + { + "name": "wp-phpunit/wp-phpunit", + "version": "6.6.2", + "source": { + "type": "git", + "url": "https://github.com/wp-phpunit/wp-phpunit.git", + "reference": "7a1d3a2150033a3d3e19de40aa5b2ef2fee36bc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/7a1d3a2150033a3d3e19de40aa5b2ef2fee36bc3", + "reference": "7a1d3a2150033a3d3e19de40aa5b2ef2fee36bc3", + "shasum": "" + }, + "type": "library", + "autoload": { + "files": [ + "__loaded.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Evan Mattson", + "email": "me@aaemnnost.tv" + }, + { + "name": "WordPress Community", + "homepage": "https://wordpress.org/about/" + } + ], + "description": "WordPress core PHPUnit library", + "homepage": "https://github.com/wp-phpunit", + "keywords": [ + "phpunit", + "test", + "wordpress" + ], + "support": { + "docs": "https://github.com/wp-phpunit/docs", + "issues": "https://github.com/wp-phpunit/issues", + "source": "https://github.com/wp-phpunit/wp-phpunit" + }, + "time": "2024-07-17T01:13:44+00:00" + }, { "name": "yoast/phpunit-polyfills", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922" + "reference": "19e6d5fb8aad31f731f774f9646a10c64a8843d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/c758753e8f9dac251fed396a73c8305af3f17922", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/19e6d5fb8aad31f731f774f9646a10c64a8843d2", + "reference": "19e6d5fb8aad31f731f774f9646a10c64a8843d2", "shasum": "" }, "require": { - "php": ">=5.6", - "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" + "php": ">=7.0", + "phpunit/phpunit": "^6.4.4 || ^7.0 || ^8.0 || ^9.0 || ^11.0" }, "require-dev": { - "yoast/yoastcs": "^2.3.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -2647,9 +2697,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2023-06-06T20:28:24+00:00" + "time": "2024-09-07T00:24:25+00:00" } ], "aliases": [], diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c002c84c..8a4535a8 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -9,7 +9,8 @@ // Require composer dependencies. require_once dirname( __DIR__ ) . '/vendor/autoload.php'; -$_tests_dir = getenv( 'WP_TESTS_DIR' ); +$_wp_tests_dir = getenv( 'WP_TESTS_DIR' ); +$_tests_dir = $_wp_tests_dir ? $_wp_tests_dir : getenv( 'WP_PHPUNIT__DIR' ); if ( ! $_tests_dir ) { $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; @@ -38,9 +39,8 @@ function _manually_load_plugin() { tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); -// Add TestCase classes. -require_once __DIR__ . '/workflow-test-case.php'; -require_once __DIR__ . '/rest-test-case.php'; +// Start up the WP testing environment. +require "{$_tests_dir}/includes/bootstrap.php"; // Allow wp_mail() in tests from a valid domain name tests_add_filter( @@ -50,5 +50,6 @@ function () { } ); -// Start up the WP testing environment. -require "{$_tests_dir}/includes/bootstrap.php"; +// Add TestCase classes. +require_once __DIR__ . '/workflow-test-case.php'; +require_once __DIR__ . '/rest-test-case.php'; diff --git a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php index b5df9378..292136f0 100644 --- a/tests/modules/custom-status/meta/test-required-metadata-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-metadata-id-handler.php @@ -9,35 +9,17 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\CustomStatus\Meta\RequiredMetadataIdHandler; -use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; -use WP_UnitTestCase; - -class RequiredMetadataIdHandlerTest extends WP_UnitTestCase { - - /** - * Before each test, ensure default custom statuses are available and reset all module options. - */ - protected function setUp(): void { - parent::setUp(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); - - // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed - // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes - // a status and it's the only status existing, which can cause errors due to status restrictions. - CustomStatus::setup_install(); - } +class RequiredMetadataIdHandlerTest extends WorkflowTestCase { public function test_remove_deleted_metadata_from_required_metadata() { - $meta_id = 1; + $meta_id = 1; $custom_status_term = CustomStatus::add_custom_status( [ - 'name' => 'Test Custom Status', - 'slug' => 'test-custom-status', - 'description' => 'Test Description.', + 'name' => 'Test Custom Status', + 'slug' => 'test-custom-status', + 'description' => 'Test Description.', 'required_metadata_ids' => [ $meta_id ], ] ); - $term_id = $custom_status_term->term_id; + $term_id = $custom_status_term->term_id; RequiredMetadataIdHandler::remove_deleted_metadata_from_required_metadata( $meta_id ); @@ -46,7 +28,5 @@ public function test_remove_deleted_metadata_from_required_metadata() { $this->assertEquals( 'Test Custom Status', $updated_term->name ); $this->assertEquals( 'Test Description.', $updated_term->description ); $this->assertEmpty( $updated_term->meta['required_metadata_ids'] ); - - CustomStatus::delete_custom_status( $term_id ); } } diff --git a/tests/modules/custom-status/meta/test-required-user-id-handler.php b/tests/modules/custom-status/meta/test-required-user-id-handler.php index b8602358..03d8cd96 100644 --- a/tests/modules/custom-status/meta/test-required-user-id-handler.php +++ b/tests/modules/custom-status/meta/test-required-user-id-handler.php @@ -9,35 +9,17 @@ use VIPWorkflow\Modules\CustomStatus; use VIPWorkflow\Modules\CustomStatus\Meta\RequiredUserIdHandler; -use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; -use WP_UnitTestCase; - -class RequiredUserIdHandlerTest extends WP_UnitTestCase { - - /** - * Before each test, ensure default custom statuses are available and reset all module options. - */ - protected function setUp(): void { - parent::setUp(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); - - // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed - // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes - // a status and it's the only status existing, which can cause errors due to status restrictions. - CustomStatus::setup_install(); - } +class RequiredUserIdHandlerTest extends WorkflowTestCase { public function test_remove_deleted_user_from_required_users_no_reassigned_user() { - $deleted_user_id = 1; + $deleted_user_id = 1; $custom_status_term = CustomStatus::add_custom_status( [ - 'name' => 'Test Custom Status', - 'slug' => 'test-custom-status', - 'description' => 'Test Description.', + 'name' => 'Test Custom Status', + 'slug' => 'test-custom-status', + 'description' => 'Test Description.', 'required_user_ids' => [ $deleted_user_id ], ] ); - $term_id = $custom_status_term->term_id; + $term_id = $custom_status_term->term_id; RequiredUserIdHandler::remove_deleted_user_from_required_users( $deleted_user_id, null ); @@ -46,20 +28,18 @@ public function test_remove_deleted_user_from_required_users_no_reassigned_user( $this->assertEquals( 'Test Custom Status', $updated_term->name ); $this->assertEquals( 'Test Description.', $updated_term->description ); $this->assertEmpty( $updated_term->meta['required_user_ids'] ); - - CustomStatus::delete_custom_status( $term_id ); } public function test_remove_deleted_user_from_required_users_with_reassigned_user() { - $deleted_user_id = 1; + $deleted_user_id = 1; $reassigned_user_id = 2; $custom_status_term = CustomStatus::add_custom_status( [ - 'name' => 'Test Custom Status', - 'slug' => 'test-custom-status', - 'description' => 'Test Description.', + 'name' => 'Test Custom Status', + 'slug' => 'test-custom-status', + 'description' => 'Test Description.', 'required_user_ids' => [ $deleted_user_id ], ] ); - $term_id = $custom_status_term->term_id; + $term_id = $custom_status_term->term_id; RequiredUserIdHandler::remove_deleted_user_from_required_users( $deleted_user_id, $reassigned_user_id ); @@ -69,7 +49,5 @@ public function test_remove_deleted_user_from_required_users_with_reassigned_use $this->assertEquals( 'Test Description.', $updated_term->description ); $this->assertCount( 1, $updated_term->meta['required_user_ids'] ); $this->assertEquals( $reassigned_user_id, $updated_term->meta['required_user_ids'][0] ); - - CustomStatus::delete_custom_status( $term_id ); } } diff --git a/tests/modules/custom-status/rest/test-custom-status-endpoint.php b/tests/modules/custom-status/rest/test-custom-status-endpoint.php index 2211b545..3d9bad74 100644 --- a/tests/modules/custom-status/rest/test-custom-status-endpoint.php +++ b/tests/modules/custom-status/rest/test-custom-status-endpoint.php @@ -17,34 +17,21 @@ */ class CustomStatusRestApiTest extends RestTestCase { - /** - * Before each test, ensure default custom statuses are available and reset all module options. - */ - protected function setUp(): void { - parent::setUp(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); - - // Normally custom statuses are installed on 'admin_init', which is only run when a page is accessed - // in the admin web interface. Manually install them here. This avoid issues when a test creates or deletes - // a status and it's the only status existing, which can cause errors due to status restrictions. - CustomStatus::setup_install(); - } - public function test_create_custom_status_with_optional_fields() { $editorial_metadata_term = EditorialMetadata::insert_editorial_metadata_term( [ 'name' => 'Test Metadata 1', 'description' => 'A test metadata for testing', 'type' => 'text', ] ); - $admin_user_id = self::create_user( 'test-admin', [ 'role' => 'administrator' ] ); + $admin_user_id = $this->factory()->user->create( [ + 'role' => 'administrator', + ] ); $request = new WP_REST_Request( 'POST', sprintf( '/%s/%s', VIP_WORKFLOW_REST_NAMESPACE, 'custom-status' ) ); $request->set_body_params( [ - 'name' => 'test-status', - 'description' => 'A test status for testing', - 'required_user_ids' => [ $admin_user_id ], + 'name' => 'test-status', + 'description' => 'A test status for testing', + 'required_user_ids' => [ $admin_user_id ], 'required_metadata_ids' => [ $editorial_metadata_term->term_id ], ] ); @@ -68,9 +55,6 @@ public function test_create_custom_status_with_optional_fields() { $this->assertEquals( $admin_user_id, $created_term->meta['required_user_ids'][0] ); $this->assertCount( 1, $created_term->meta['required_metadata_ids'] ); $this->assertEquals( $editorial_metadata_term->term_id, $created_term->meta['required_metadata_ids'][0] ); - - CustomStatus::delete_custom_status( $term_id ); - EditorialMetadata::delete_editorial_metadata_term( $editorial_metadata_term->term_id ); } public function test_create_custom_status() { @@ -94,19 +78,19 @@ public function test_create_custom_status() { $created_term = CustomStatus::get_custom_status_by( 'id', $term_id ); $this->assertEquals( 'test-status', $created_term->name ); - - CustomStatus::delete_custom_status( $term_id ); } public function test_update_custom_status() { $custom_status_term = CustomStatus::add_custom_status( [ - 'name' => 'Test Custom Status', - 'slug' => 'test-custom-status', - 'description' => 'Test Description.', + 'name' => 'Test Custom Status', + 'slug' => 'test-custom-status', + 'description' => 'Test Description.', ] ); $term_id = $custom_status_term->term_id; - $editor_user_id = self::create_user( 'test-editor', [ 'role' => 'editor' ] ); + $editor_user_id = $this->factory()->user->create( [ + 'role' => 'editor', + ] ); $request = new WP_REST_Request( 'PUT', sprintf( '/%s/%s/%d', VIP_WORKFLOW_REST_NAMESPACE, 'custom-status', $term_id ) ); $request->set_body_params( [ @@ -129,8 +113,6 @@ public function test_update_custom_status() { $this->assertEquals( 'Test Description 2!', $updated_term->description ); $this->assertCount( 1, $updated_term->meta['required_user_ids'] ); $this->assertEquals( $editor_user_id, $updated_term->meta['required_user_ids'][0] ); - - CustomStatus::delete_custom_status( $term_id ); } public function test_delete_custom_status() { @@ -193,9 +175,5 @@ public function test_reorder_custom_status() { $this->assertEquals( $term3->term_id, $reordered_custom_statuses[0]->term_id ); $this->assertEquals( $term1->term_id, $reordered_custom_statuses[1]->term_id ); $this->assertEquals( $term2->term_id, $reordered_custom_statuses[2]->term_id ); - - CustomStatus::delete_custom_status( $term1->term_id ); - CustomStatus::delete_custom_status( $term2->term_id ); - CustomStatus::delete_custom_status( $term3->term_id ); } } diff --git a/tests/modules/custom-status/test-custom-status-transitions.php b/tests/modules/custom-status/test-custom-status-transitions.php index 2b5b6ae1..c610ef47 100644 --- a/tests/modules/custom-status/test-custom-status-transitions.php +++ b/tests/modules/custom-status/test-custom-status-transitions.php @@ -15,7 +15,10 @@ class CustomStatusTransitionsTest extends WorkflowTestCase { public function test_transition_restrictions_as_privileged_user() { - $admin_user_id = self::create_user( 'test-admin', [ 'role' => 'administrator' ] ); + $admin_user_id = $this->factory()->user->create( [ + 'role' => 'administrator', + ] ); + wp_set_current_user( $admin_user_id ); // Setup statuses, with the second status requiring admin user permissions @@ -62,18 +65,13 @@ public function test_transition_restrictions_as_privileged_user() { $this->assertEquals( $post_id, $transtion_to_status_3_result ); $this->assertEquals( 'status-3', get_post_status( $post_id ) ); - - // Cleanup - CustomStatus::delete_custom_status( $status_1->term_id ); - CustomStatus::delete_custom_status( $status_2_restricted->term_id ); - CustomStatus::delete_custom_status( $status_3->term_id ); - - wp_set_current_user( null ); - wp_delete_user( $admin_user_id ); } public function test_transition_restrictions_as_unprivileged_user() { - $author_user_id = self::create_user( 'test-unprivileged-author', [ 'role' => 'author' ] ); + $author_user_id = $this->factory()->user->create( [ + 'role' => 'author', + ] ); + wp_set_current_user( $author_user_id ); // Setup statuses, with the second status requiring admin user permissions @@ -83,7 +81,10 @@ public function test_transition_restrictions_as_unprivileged_user() { 'slug' => 'status-1', ] ); - $admin_user_id = self::create_user( 'test-admin', [ 'role' => 'administrator' ] ); + $admin_user_id = $this->factory()->user->create( [ + 'role' => 'administrator', + ] ); + $status_2_restricted = CustomStatus::add_custom_status( [ 'name' => 'Status 2 (restricted)', 'position' => -2, @@ -121,10 +122,5 @@ public function test_transition_restrictions_as_unprivileged_user() { $this->assertInstanceOf( 'WP_Error', $transtion_to_status_3_result ); $this->assertEquals( 'status-2-restricted', get_post_status( $post_id ) ); - - // Cleanup - CustomStatus::delete_custom_status( $status_1->term_id ); - CustomStatus::delete_custom_status( $status_2_restricted->term_id ); - CustomStatus::delete_custom_status( $status_3->term_id ); } } diff --git a/tests/modules/editorial-metadata/test-editorial-metadata-endpoint.php b/tests/modules/editorial-metadata/test-editorial-metadata-endpoint.php index 035e5842..38f736ec 100644 --- a/tests/modules/editorial-metadata/test-editorial-metadata-endpoint.php +++ b/tests/modules/editorial-metadata/test-editorial-metadata-endpoint.php @@ -41,8 +41,6 @@ public function test_create_editorial_metadata() { $this->assertEquals( 'A test metadata for testing', $created_term->description ); $this->assertEquals( 'text', $created_term->meta['type'] ); $this->assertEquals( 'vw_editorial_meta_text_' . $term_id, $created_term->meta['postmeta_key'] ); - - EditorialMetadata::delete_editorial_metadata_term( $term_id ); } public function test_update_editorial_metadata() { @@ -74,8 +72,6 @@ public function test_update_editorial_metadata() { $this->assertEquals( 'Test Description 2!', $updated_term->description ); $this->assertEquals( 'text', $updated_term->meta['type'] ); $this->assertEquals( 'vw_editorial_meta_text_' . $term_id, $updated_term->meta['postmeta_key'] ); - - EditorialMetadata::delete_editorial_metadata_term( $term_id ); } public function test_delete_editorial_metadata() { diff --git a/tests/modules/notifications/test-notifications.php b/tests/modules/notifications/test-notifications.php index 236b41d4..8abfc941 100644 --- a/tests/modules/notifications/test-notifications.php +++ b/tests/modules/notifications/test-notifications.php @@ -12,30 +12,26 @@ use VIPWorkflow\Modules\Settings; use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; use WP_Error; -use WP_UnitTestCase; -class NotificationsTest extends WP_UnitTestCase { +class NotificationsTest extends WorkflowTestCase { protected function tearDown(): void { parent::tearDown(); reset_phpmailer_instance(); - - // Reset all module options - OptionsUtilities::reset_all_module_options(); } public function test_validate_get_notification_footer() { $expected_result = "\r\n--------------------\r\nYou are receiving this email because a notification was configured via the VIP Workflow Plugin.\r\n"; - $result = Notifications::get_notification_footer(); + $result = Notifications::get_notification_footer(); $this->assertTrue( $result === $expected_result ); } public function test_send_emails() { $recipients = [ 'test1@gmail.com', 'test2@gmail.com', 'test3@gmail.com' ]; - $subject = 'Test Subject'; - $body = 'Test Body'; + $subject = 'Test Subject'; + $body = 'Test Body'; Notifications::send_emails( $recipients, $subject, $body ); diff --git a/tests/rest-test-case.php b/tests/rest-test-case.php index 494d890d..e0dd1c84 100644 --- a/tests/rest-test-case.php +++ b/tests/rest-test-case.php @@ -7,7 +7,6 @@ namespace VIPWorkflow\Tests; -use PHPUnit\Framework\TestCase; use WP_REST_Request; use WP_REST_Server; @@ -25,11 +24,9 @@ protected function setUp(): void { parent::setUp(); // Create an administrative user for tests to use - $this->administrator_user_id = $this->create_user( 'admin-rest-user', [ - 'user_pass' => wp_generate_password(), - 'user_email' => 'admin-rest-user@example.com', - 'role' => 'administrator', - ]); + $this->administrator_user_id = $this->factory()->user->create( [ + 'role' => 'administrator', + ] ); // Create a new REST server $this->server = new WP_REST_Server(); diff --git a/tests/workflow-test-case.php b/tests/workflow-test-case.php index d4c851a5..15fa360d 100644 --- a/tests/workflow-test-case.php +++ b/tests/workflow-test-case.php @@ -7,60 +7,23 @@ namespace VIPWorkflow\Tests; -use PHPUnit\Framework\TestCase; +use VIPWorkflow\Modules\CustomStatus; +use VIPWorkflow\Modules\Shared\PHP\OptionsUtilities; +use WP_UnitTestCase; /** * Extension of TestCase with helper methods */ -class WorkflowTestCase extends TestCase { - protected $user_ids_to_cleanup = []; - +class WorkflowTestCase extends WP_UnitTestCase { /** * Before each test, register REST endpoints. */ protected function setUp(): void { - parent::setUp(); - - // Always ensure we're starting as an unauthenticated user unless specifically set - wp_set_current_user( null ); - } - - /** - * After each test, reset REST endpoints. - */ - protected function tearDown(): void { - // Remove any users created during this test - foreach ( $this->user_ids_to_cleanup as $user_id ) { - if ( is_multisite() ) { - // Ensure user is fully deleted in multisite tests - wpmu_delete_user( $user_id ); - } else { - wp_delete_user( $user_id ); - } - } - - $this->user_ids_to_cleanup = []; + // Reset module options at the start of each test, which allows setup_install() to install a default + // set of custom statuses. Tested code expects some default statuses to always be available. + OptionsUtilities::reset_all_module_options(); + CustomStatus::setup_install(); - parent::tearDown(); - } - - protected function create_user( $username, $args = [] ) { - $default_args = [ - 'user_login' => $username, - 'user_pass' => 'password', - 'display_name' => $username, - 'user_email' => sprintf( '%s@example.com', $username ), - 'role' => 'editor', - ]; - - $user_id = wp_insert_user( array_merge( $default_args, $args ) ); - - if ( is_wp_error( $user_id ) ) { - throw new \Exception( esc_html( $user_id->get_error_message() ) ); - } - - $this->user_ids_to_cleanup[] = $user_id; - - return $user_id; + parent::setUp(); } }