From 7ac72d397d0b240dd2af9f80dc76f9eee7770e0d Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 25 Jun 2024 11:07:13 +1000 Subject: [PATCH 01/15] Disable the php cs diff until we lint everything --- .github/workflows/php-tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index 0077d875..dc91c783 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -49,8 +49,9 @@ jobs: env: WP_ENV_CORE: WordPress/WordPress#${{ matrix.wp }} - - name: Run PHPCS diff tests - run: bash bin/phpcs-diff.sh + # Commented out until we finish linting all the files + # - name: Run PHPCS diff tests + # run: bash bin/phpcs-diff.sh - name: Run PHPUnit tests (single site) run: composer integration From 60f04b0c947fcdcf95423049e0e7278963abf892 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 25 Jun 2024 11:41:42 +1000 Subject: [PATCH 02/15] Correct the vip site detection check to work for vip dev-env as well --- common/php/class-module.php | 16 ++++++++++++---- modules/settings/settings.php | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/common/php/class-module.php b/common/php/class-module.php index 619ee327..d9a3e262 100644 --- a/common/php/class-module.php +++ b/common/php/class-module.php @@ -76,12 +76,20 @@ protected function is_analytics_enabled() { * Check if the site is a WPVIP site. * * @since 0.10.0 + * + * @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() { - return defined( 'WPCOM_IS_VIP_ENV' ) && constant( 'WPCOM_IS_VIP_ENV' ) === true - && defined( 'WPCOM_SANDBOXED' ) && constant( 'WPCOM_SANDBOXED' ) === false - && defined( 'FILES_CLIENT_SITE_ID' ); + 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; } /** diff --git a/modules/settings/settings.php b/modules/settings/settings.php index cd3e22bf..c389ec12 100644 --- a/modules/settings/settings.php +++ b/modules/settings/settings.php @@ -23,7 +23,7 @@ public function __construct() { 'default_options' => array( 'enabled' => 'on', 'vip_features' => $this->is_vip_site() ? 'on' : 'off', - 'analytics' => $this->is_vip_site() ? 'on' : 'off', + 'analytics' => $this->is_vip_site( true ) ? 'on' : 'off', ), 'configure_page_cb' => 'print_default_settings', 'autoload' => true, From 7b42f6a8d81b1aa6a56bf3b2073e6b177fa6cd6f Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Tue, 25 Jun 2024 15:14:05 +1000 Subject: [PATCH 03/15] Lint the custom-status module --- composer.json | 4 +- modules/custom-status/custom-status.php | 2852 ++++++++++++----------- phpcs.xml.dist | 6 +- 3 files changed, 1490 insertions(+), 1372 deletions(-) diff --git a/composer.json b/composer.json index d1e881a2..581e2020 100644 --- a/composer.json +++ b/composer.json @@ -21,10 +21,10 @@ }, "scripts": { "cs": [ - "@php ./vendor/bin/phpcs -p -s -v -n . --standard=\"WordPress-VIP-Go\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*\"" + "@php ./vendor/bin/phpcs -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*\"" ], "cbf": [ - "@php ./vendor/bin/phpcbf -p -s -v -n . --standard=\"WordPress-VIP-Go\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*\"" + "@php ./vendor/bin/phpcbf -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*\"" ], "integration": "wp-env run tests-cli --env-cwd=wp-content/plugins/Edit-Flow ./vendor/bin/phpunit", "integration-ms": "wp-env run tests-cli --env-cwd=wp-content/plugins/Edit-Flow /bin/bash -c 'WP_MULTISITE=1 ./vendor/bin/phpunit'" diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index df701d7a..470be27f 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -9,332 +9,337 @@ * - Ensure all of the form processing uses our messages functionality */ +if ( ! class_exists( 'EF_Custom_Status' ) ) { - if ( !class_exists( 'EF_Custom_Status' ) ) { + class EF_Custom_Status extends EF_Module { -class EF_Custom_Status extends EF_Module { + public $module; - var $module; + private $custom_statuses_cache = array(); - private $custom_statuses_cache = array(); + // This is taxonomy name used to store all our custom statuses + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase + const taxonomy_key = 'post_status'; - // This is taxonomy name used to store all our custom statuses - const taxonomy_key = 'post_status'; + /** + * Register the module with Edit Flow but don't do anything else + */ + public function __construct() { - /** - * Register the module with Edit Flow but don't do anything else - */ - function __construct() { - - $this->module_url = $this->get_module_url( __FILE__ ); - // Register the module with Edit Flow - $args = array( - 'title' => __( 'Custom Statuses', 'edit-flow' ), - 'short_description' => __( 'Create custom post statuses to define the stages of your workflow.', 'edit-flow' ), - 'extended_description' => __( 'Create your own post statuses to add structure your publishing workflow. You can change existing or add new ones anytime, and drag and drop to change their order.', 'edit-flow' ), - 'module_url' => $this->module_url, - 'img_url' => $this->module_url . 'lib/custom_status_s128.png', - 'slug' => 'custom-status', - 'default_options' => array( - 'enabled' => 'on', - 'default_status' => 'pitch', - 'always_show_dropdown' => 'off', - 'post_types' => array( - 'post' => 'on', - 'page' => 'on', + $this->module_url = $this->get_module_url( __FILE__ ); + // Register the module with Edit Flow + $args = array( + 'title' => __( 'Custom Statuses', 'edit-flow' ), + 'short_description' => __( 'Create custom post statuses to define the stages of your workflow.', 'edit-flow' ), + 'extended_description' => __( 'Create your own post statuses to add structure your publishing workflow. You can change existing or add new ones anytime, and drag and drop to change their order.', 'edit-flow' ), + 'module_url' => $this->module_url, + 'img_url' => $this->module_url . 'lib/custom_status_s128.png', + 'slug' => 'custom-status', + 'default_options' => array( + 'enabled' => 'on', + 'default_status' => 'pitch', + 'always_show_dropdown' => 'off', + 'post_types' => array( + 'post' => 'on', + 'page' => 'on', + ), ), - ), - 'post_type_support' => 'ef_custom_statuses', // This has been plural in all of our docs - 'configure_page_cb' => 'print_configure_view', - 'configure_link_text' => __( 'Edit Statuses', 'edit-flow' ), - 'messages' => array( - 'status-added' => __( 'Post status created.', 'edit-flow' ), - 'status-missing' => __( "Post status doesn't exist.", 'edit-flow' ), - 'default-status-changed' => __( 'Default post status has been changed.', 'edit-flow'), - 'term-updated' => __( "Post status updated.", 'edit-flow' ), - 'status-deleted' => __( 'Post status deleted.', 'edit-flow' ), - 'status-position-updated' => __( "Status order updated.", 'edit-flow' ), - ), - 'autoload' => false, - 'settings_help_tab' => array( - 'id' => 'ef-custom-status-overview', - 'title' => __('Overview', 'edit-flow'), - 'content' => __('

Edit Flow’s custom statuses allow you to define the most important stages of your editorial workflow. Out of the box, WordPress only offers “Draft” and “Pending Review” as post states. With custom statuses, you can create your own post states like “In Progress”, “Pitch”, or “Waiting for Edit” and keep or delete the originals. You can also drag and drop statuses to set the best order for your workflow.

Custom statuses are fully integrated into the rest of Edit Flow and the WordPress admin. On the calendar and story budget, you can filter your view to see only posts of a specific post state. Furthermore, email notifications can be sent to a specific group of users when a post changes state.

', 'edit-flow'), + 'post_type_support' => 'ef_custom_statuses', // This has been plural in all of our docs + 'configure_page_cb' => 'print_configure_view', + 'configure_link_text' => __( 'Edit Statuses', 'edit-flow' ), + 'messages' => array( + 'status-added' => __( 'Post status created.', 'edit-flow' ), + 'status-missing' => __( "Post status doesn't exist.", 'edit-flow' ), + 'default-status-changed' => __( 'Default post status has been changed.', 'edit-flow' ), + 'term-updated' => __( 'Post status updated.', 'edit-flow' ), + 'status-deleted' => __( 'Post status deleted.', 'edit-flow' ), + 'status-position-updated' => __( 'Status order updated.', 'edit-flow' ), ), - 'settings_help_sidebar' => __( '

For more information:

Custom Status Documentation

Edit Flow Forum

Edit Flow on Github

', 'edit-flow' ), - ); - $this->module = EditFlow()->register_module( 'custom_status', $args ); - } + 'autoload' => false, + 'settings_help_tab' => array( + 'id' => 'ef-custom-status-overview', + 'title' => __( 'Overview', 'edit-flow' ), + 'content' => __( '

Edit Flow’s custom statuses allow you to define the most important stages of your editorial workflow. Out of the box, WordPress only offers “Draft” and “Pending Review” as post states. With custom statuses, you can create your own post states like “In Progress”, “Pitch”, or “Waiting for Edit” and keep or delete the originals. You can also drag and drop statuses to set the best order for your workflow.

Custom statuses are fully integrated into the rest of Edit Flow and the WordPress admin. On the calendar and story budget, you can filter your view to see only posts of a specific post state. Furthermore, email notifications can be sent to a specific group of users when a post changes state.

', 'edit-flow' ), + ), + 'settings_help_sidebar' => __( '

For more information:

Custom Status Documentation

Edit Flow Forum

Edit Flow on Github

', 'edit-flow' ), + ); + $this->module = EditFlow()->register_module( 'custom_status', $args ); + } - /** - * Initialize the EF_Custom_Status class if the module is active - */ - function init() { - global $edit_flow; + /** + * Initialize the EF_Custom_Status class if the module is active + */ + public function init() { + global $edit_flow; - // Register custom statuses as a taxonomy - $this->register_custom_statuses(); + // Register custom statuses as a taxonomy + $this->register_custom_statuses(); - // Register our settings - add_action( 'admin_init', array( $this, 'register_settings' ) ); + // Register our settings + add_action( 'admin_init', array( $this, 'register_settings' ) ); - if ( ! $this->disable_custom_statuses_for_post_type() ) { - // Load CSS and JS resources that we probably need in the admin page - add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); + if ( ! $this->disable_custom_statuses_for_post_type() ) { + // Load CSS and JS resources that we probably need in the admin page + add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); - // Assets for block editor UI. - add_action( 'enqueue_block_editor_assets', array( $this, 'load_scripts_for_block_editor') ); + // Assets for block editor UI. + add_action( 'enqueue_block_editor_assets', array( $this, 'load_scripts_for_block_editor' ) ); - // Assets for iframed block editor and editor UI. - add_action( 'enqueue_block_editor_assets', array( $this, 'load_styles_for_block_editor') ); - } + // Assets for iframed block editor and editor UI. + add_action( 'enqueue_block_editor_assets', array( $this, 'load_styles_for_block_editor' ) ); + } - add_action( 'admin_notices', array( $this, 'no_js_notice' ) ); - add_action( 'admin_print_scripts', array( $this, 'post_admin_header' ) ); - - // Add custom statuses to the post states. - add_filter( 'display_post_states', array( $this, 'add_status_to_post_states' ), 10, 2 ); - - // Methods for handling the actions of creating, making default, and deleting post stati - add_action( 'admin_init', array( $this, 'handle_add_custom_status' ) ); - add_action( 'admin_init', array( $this, 'handle_edit_custom_status' ) ); - add_action( 'admin_init', array( $this, 'handle_make_default_custom_status' ) ); - add_action( 'admin_init', array( $this, 'handle_delete_custom_status' ) ); - add_action( 'wp_ajax_update_status_positions', array( $this, 'handle_ajax_update_status_positions' ) ); - add_action( 'wp_ajax_inline_save_status', array( $this, 'ajax_inline_save_status' ) ); - - // These seven-ish methods are hacks for fixing bugs in WordPress core - add_action( 'admin_init', array( $this, 'check_timestamp_on_publish' ) ); - add_filter( 'wp_insert_post_data', array( $this, 'fix_custom_status_timestamp' ), 10, 2 ); - add_filter( 'wp_insert_post_data', array( $this, 'maybe_keep_post_name_empty' ), 10, 2 ); - add_filter( 'pre_wp_unique_post_slug', array( $this, 'fix_unique_post_slug' ), 10, 6 ); - add_filter( 'preview_post_link', array( $this, 'fix_preview_link_part_one' ) ); - add_filter( 'post_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 ); - add_filter( 'page_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 ); - add_filter( 'post_type_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 ); - add_filter( 'preview_post_link', array( $this, 'fix_preview_link_part_three' ), 11, 2 ); - add_filter( 'get_sample_permalink', array( $this, 'fix_get_sample_permalink' ), 10, 5 ); - add_filter( 'get_sample_permalink_html', array( $this, 'fix_get_sample_permalink_html' ), 10, 5); - add_filter( 'post_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 ); - add_filter( 'page_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 ); - - // Pagination for custom post statuses when previewing posts - add_filter( 'wp_link_pages_link', array( $this, 'modify_preview_link_pagination_url' ), 10, 2 ); - } + add_action( 'admin_notices', array( $this, 'no_js_notice' ) ); + add_action( 'admin_print_scripts', array( $this, 'post_admin_header' ) ); + + // Add custom statuses to the post states. + add_filter( 'display_post_states', array( $this, 'add_status_to_post_states' ), 10, 2 ); + + // Methods for handling the actions of creating, making default, and deleting post stati + add_action( 'admin_init', array( $this, 'handle_add_custom_status' ) ); + add_action( 'admin_init', array( $this, 'handle_edit_custom_status' ) ); + add_action( 'admin_init', array( $this, 'handle_make_default_custom_status' ) ); + add_action( 'admin_init', array( $this, 'handle_delete_custom_status' ) ); + add_action( 'wp_ajax_update_status_positions', array( $this, 'handle_ajax_update_status_positions' ) ); + add_action( 'wp_ajax_inline_save_status', array( $this, 'ajax_inline_save_status' ) ); + + // These seven-ish methods are hacks for fixing bugs in WordPress core + add_action( 'admin_init', array( $this, 'check_timestamp_on_publish' ) ); + add_filter( 'wp_insert_post_data', array( $this, 'fix_custom_status_timestamp' ), 10, 2 ); + add_filter( 'wp_insert_post_data', array( $this, 'maybe_keep_post_name_empty' ), 10, 2 ); + add_filter( 'pre_wp_unique_post_slug', array( $this, 'fix_unique_post_slug' ), 10, 6 ); + add_filter( 'preview_post_link', array( $this, 'fix_preview_link_part_one' ) ); + add_filter( 'post_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 ); + add_filter( 'page_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 ); + add_filter( 'post_type_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 ); + add_filter( 'preview_post_link', array( $this, 'fix_preview_link_part_three' ), 11, 2 ); + add_filter( 'get_sample_permalink', array( $this, 'fix_get_sample_permalink' ), 10, 5 ); + add_filter( 'get_sample_permalink_html', array( $this, 'fix_get_sample_permalink_html' ), 10, 5 ); + add_filter( 'post_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 ); + add_filter( 'page_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 ); + + // Pagination for custom post statuses when previewing posts + add_filter( 'wp_link_pages_link', array( $this, 'modify_preview_link_pagination_url' ), 10, 2 ); + } - /** - * Create the default set of custom statuses the first time the module is loaded - * - * @since 0.7 - */ - function install() { - - $default_terms = array( - array( - 'term' => __( 'Pitch', 'edit-flow' ), - 'args' => array( - 'slug' => 'pitch', - 'description' => __( 'Idea proposed; waiting for acceptance.', 'edit-flow' ), - 'position' => 1, + /** + * Create the default set of custom statuses the first time the module is loaded + * + * @since 0.7 + */ + public function install() { + + $default_terms = array( + array( + 'term' => __( 'Pitch', 'edit-flow' ), + 'args' => array( + 'slug' => 'pitch', + 'description' => __( 'Idea proposed; waiting for acceptance.', 'edit-flow' ), + 'position' => 1, + ), ), - ), - array( - 'term' => __( 'Assigned', 'edit-flow' ), - 'args' => array( - 'slug' => 'assigned', - 'description' => __( 'Post idea assigned to writer.', 'edit-flow' ), - 'position' => 2, + array( + 'term' => __( 'Assigned', 'edit-flow' ), + 'args' => array( + 'slug' => 'assigned', + 'description' => __( 'Post idea assigned to writer.', 'edit-flow' ), + 'position' => 2, + ), ), - ), - array( - 'term' => __( 'In Progress', 'edit-flow' ), - 'args' => array( - 'slug' => 'in-progress', - 'description' => __( 'Writer is working on the post.', 'edit-flow' ), - 'position' => 3, + array( + 'term' => __( 'In Progress', 'edit-flow' ), + 'args' => array( + 'slug' => 'in-progress', + 'description' => __( 'Writer is working on the post.', 'edit-flow' ), + 'position' => 3, + ), ), - ), - array( - 'term' => __( 'Draft', 'edit-flow' ), - 'args' => array( - 'slug' => 'draft', - 'description' => __( 'Post is a draft; not ready for review or publication.', 'edit-flow' ), - 'position' => 4, + array( + 'term' => __( 'Draft', 'edit-flow' ), + 'args' => array( + 'slug' => 'draft', + 'description' => __( 'Post is a draft; not ready for review or publication.', 'edit-flow' ), + 'position' => 4, + ), ), - ), - array( - 'term' => __( 'Pending Review' ), - 'args' => array( - 'slug' => 'pending', - 'description' => __( 'Post needs to be reviewed by an editor.', 'edit-flow' ), - 'position' => 5, + array( + 'term' => __( 'Pending Review' ), + 'args' => array( + 'slug' => 'pending', + 'description' => __( 'Post needs to be reviewed by an editor.', 'edit-flow' ), + '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['term'], self::taxonomy_key ) ) - $this->add_custom_status( $term['term'], $term['args'] ); + // Okay, now add the default statuses to the db if they don't already exist + foreach ( $default_terms as $term ) { + if ( ! term_exists( $term['term'], self::taxonomy_key ) ) { + $this->add_custom_status( $term['term'], $term['args'] ); + } + } } - } - - /** - * Upgrade our data in case we need to - * - * @since 0.7 - */ - function upgrade( $previous_version ) { - global $edit_flow; + /** + * Upgrade our data in case we need to + * + * @since 0.7 + */ + public function upgrade( $previous_version ) { + global $edit_flow; + + // Upgrade path to v0.7 + if ( version_compare( $previous_version, '0.7', '<' ) ) { + // Migrate dropdown visibility option + $dropdown_visible = get_option( 'edit_flow_status_dropdown_visible' ); + if ( $dropdown_visible ) { + $dropdown_visible = 'on'; + } else { + $dropdown_visible = 'off'; + } + $edit_flow->update_module_option( $this->module->name, 'always_show_dropdown', $dropdown_visible ); + delete_option( 'edit_flow_status_dropdown_visible' ); + // Migrate default status option + $default_status = get_option( 'edit_flow_custom_status_default_status' ); + if ( $default_status ) { + $edit_flow->update_module_option( $this->module->name, 'default_status', $default_status ); + } + delete_option( 'edit_flow_custom_status_default_status' ); - // Upgrade path to v0.7 - if ( version_compare( $previous_version, '0.7' , '<' ) ) { - // Migrate dropdown visibility option - if ( $dropdown_visible = get_option( 'edit_flow_status_dropdown_visible' ) ) - $dropdown_visible = 'on'; - else - $dropdown_visible = 'off'; - $edit_flow->update_module_option( $this->module->name, 'always_show_dropdown', $dropdown_visible ); - delete_option( 'edit_flow_status_dropdown_visible' ); - // Migrate default status option - if ( $default_status = get_option( 'edit_flow_custom_status_default_status' ) ) - $edit_flow->update_module_option( $this->module->name, 'default_status', $default_status ); - delete_option( 'edit_flow_custom_status_default_status' ); - - // Technically we've run this code before so we don't want to auto-install new data - $edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); - } - // Upgrade path to v0.7.4 - if ( version_compare( $previous_version, '0.7.4', '<' ) ) { - // Custom status descriptions become base64_encoded, instead of maybe json_encoded. - $this->upgrade_074_term_descriptions( self::taxonomy_key ); + // Technically we've run this code before so we don't want to auto-install new data + $edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); + } + // Upgrade path to v0.7.4 + if ( version_compare( $previous_version, '0.7.4', '<' ) ) { + // Custom status descriptions become base64_encoded, instead of maybe json_encoded. + $this->upgrade_074_term_descriptions( self::taxonomy_key ); + } } - } + /** + * Makes the call to register_post_status to register the user's custom statuses. + * Also unregisters draft and pending, in case the user doesn't want them. + */ + public function register_custom_statuses() { + global $wp_post_statuses; - /** - * Makes the call to register_post_status to register the user's custom statuses. - * Also unregisters draft and pending, in case the user doesn't want them. - */ - function register_custom_statuses() { - global $wp_post_statuses; - - if ( $this->disable_custom_statuses_for_post_type() ) - return; - - // Register new taxonomy so that we can store all our fancy new custom statuses (or is it stati?) - if ( !taxonomy_exists( self::taxonomy_key ) ) { - $args = array( 'hierarchical' => false, - 'update_count_callback' => '_update_post_term_count', - 'label' => false, - 'query_var' => false, - 'rewrite' => false, - 'show_ui' => false - ); - register_taxonomy( self::taxonomy_key, 'post', $args ); - } + if ( $this->disable_custom_statuses_for_post_type() ) { + return; + } + + // Register new taxonomy so that we can store all our fancy new custom statuses (or is it stati?) + if ( ! taxonomy_exists( self::taxonomy_key ) ) { + $args = array( + 'hierarchical' => false, + 'update_count_callback' => '_update_post_term_count', + 'label' => false, + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => false, + ); + register_taxonomy( self::taxonomy_key, 'post', $args ); + } - if ( function_exists( 'register_post_status' ) ) { - // Users can delete draft and pending statuses if they want, so let's get rid of them - // They'll get re-added if the user hasn't "deleted" them - unset( $wp_post_statuses[ 'draft' ] ); - unset( $wp_post_statuses[ 'pending' ] ); + if ( function_exists( 'register_post_status' ) ) { + // Users can delete draft and pending statuses if they want, so let's get rid of them + // They'll get re-added if the user hasn't "deleted" them + unset( $wp_post_statuses['draft'] ); + unset( $wp_post_statuses['pending'] ); - $custom_statuses = $this->get_custom_statuses(); + $custom_statuses = $this->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 - foreach ( $custom_statuses as $status ) { - register_post_status( $status->slug, array( - 'label' => $status->name - , 'protected' => true - , '_builtin' => false - , 'label_count' => _n_noop( "{$status->name} (%s)", "{$status->name} (%s)" ) - ) ); + // 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, array( + 'label' => $status->name, + 'protected' => true, + '_builtin' => false, + 'label_count' => _n_noop( "{$status->name} (%s)", "{$status->name} (%s)" ), + ) ); + } } } - } - /** - * Whether custom post statuses should be disabled for this post type. - * Used to stop custom statuses from being registered for post types that don't support them. - * - * @since 0.7.5 - * - * @return bool - */ - function disable_custom_statuses_for_post_type( $post_type = null ) { - global $pagenow; - - // Only allow deregistering on 'edit.php' and 'post.php' - if ( ! in_array( $pagenow, array( 'edit.php', 'post.php', 'post-new.php' ) ) ) - return false; + /** + * Whether custom post statuses should be disabled for this post type. + * Used to stop custom statuses from being registered for post types that don't support them. + * + * @since 0.7.5 + * + * @return bool + */ + public function disable_custom_statuses_for_post_type( $post_type = null ) { + global $pagenow; - if ( is_null( $post_type ) ) { - $post_type = $this->get_current_post_type(); - } + // Only allow deregistering on 'edit.php' and 'post.php' + if ( ! in_array( $pagenow, array( 'edit.php', 'post.php', 'post-new.php' ) ) ) { + return false; + } - if ( $post_type && ! in_array( $post_type, $this->get_post_types_for_module( $this->module ) ) ) { - return true; - } + if ( is_null( $post_type ) ) { + $post_type = $this->get_current_post_type(); + } - return false; - } + if ( $post_type && ! in_array( $post_type, $this->get_post_types_for_module( $this->module ) ) ) { + return true; + } - /** - * Enqueue Javascript resources that we need in the admin: - * - Primary use of Javascript is to manipulate the post status dropdown on Edit Post and Manage Posts - * - jQuery Sortable plugin is used for drag and dropping custom statuses - * - We have other custom code for Quick Edit and JS niceties - */ - function action_admin_enqueue_scripts() { - // Load Javascript we need to use on the configuration views (jQuery Sortable and Quick Edit) - if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { - wp_enqueue_script( 'jquery-ui-sortable' ); - wp_enqueue_script( 'edit-flow-custom-status-configure', $this->module_url . 'lib/custom-status-configure.js', array( 'jquery', 'jquery-ui-sortable', 'edit-flow-settings-js' ), EDIT_FLOW_VERSION, true ); + return false; } - // Custom javascript to modify the post status dropdown where it shows up - if ( $this->is_whitelisted_page() ) { - wp_enqueue_script( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.js', array( 'jquery','post' ), EDIT_FLOW_VERSION, true ); - wp_localize_script('edit_flow-custom_status', '__ef_localize_custom_status', array( - 'no_change' => esc_html__( "— No Change —", 'edit-flow' ), - 'published' => esc_html__( 'Published', 'edit-flow' ), - 'save_as' => esc_html__( 'Save as', 'edit-flow' ), - 'save' => esc_html__( 'Save', 'edit-flow' ), - 'edit' => esc_html__( 'Edit', 'edit-flow' ), - 'ok' => esc_html__( 'OK', 'edit-flow' ), - 'cancel' => esc_html__( 'Cancel', 'edit-flow' ), - )); - } + /** + * Enqueue Javascript resources that we need in the admin: + * - Primary use of Javascript is to manipulate the post status dropdown on Edit Post and Manage Posts + * - jQuery Sortable plugin is used for drag and dropping custom statuses + * - We have other custom code for Quick Edit and JS niceties + */ + public function action_admin_enqueue_scripts() { + // Load Javascript we need to use on the configuration views (jQuery Sortable and Quick Edit) + if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { + wp_enqueue_script( 'jquery-ui-sortable' ); + wp_enqueue_script( 'edit-flow-custom-status-configure', $this->module_url . 'lib/custom-status-configure.js', array( 'jquery', 'jquery-ui-sortable', 'edit-flow-settings-js' ), EDIT_FLOW_VERSION, true ); + } - } + // Custom javascript to modify the post status dropdown where it shows up + if ( $this->is_whitelisted_page() ) { + wp_enqueue_script( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.js', array( 'jquery', 'post' ), EDIT_FLOW_VERSION, true ); + wp_localize_script('edit_flow-custom_status', '__ef_localize_custom_status', array( + 'no_change' => esc_html__( '— No Change —', 'edit-flow' ), + 'published' => esc_html__( 'Published', 'edit-flow' ), + 'save_as' => esc_html__( 'Save as', 'edit-flow' ), + 'save' => esc_html__( 'Save', 'edit-flow' ), + 'edit' => esc_html__( 'Edit', 'edit-flow' ), + 'ok' => esc_html__( 'OK', 'edit-flow' ), + 'cancel' => esc_html__( 'Cancel', 'edit-flow' ), + )); + } + } - function load_scripts_for_block_editor(){ - global $post; + public function load_scripts_for_block_editor() { + global $post; - wp_enqueue_script( 'edit-flow-block-custom-status-script', EDIT_FLOW_URL . 'dist/custom-status.build.js', array( 'wp-blocks', 'wp-element', 'wp-edit-post', 'wp-plugins', 'wp-components' ), EDIT_FLOW_VERSION ); + wp_enqueue_script( 'edit-flow-block-custom-status-script', EDIT_FLOW_URL . 'dist/custom-status.build.js', array( 'wp-blocks', 'wp-element', 'wp-edit-post', 'wp-plugins', 'wp-components' ), EDIT_FLOW_VERSION ); - $custom_statuses = apply_filters( 'ef_custom_status_list', $this->get_custom_statuses(), $post ); + $custom_statuses = apply_filters( 'ef_custom_status_list', $this->get_custom_statuses(), $post ); - wp_localize_script( 'edit-flow-block-custom-status-script', 'EditFlowCustomStatuses', array_values( $custom_statuses ) ); - } + wp_localize_script( 'edit-flow-block-custom-status-script', 'EditFlowCustomStatuses', array_values( $custom_statuses ) ); + } - function load_styles_for_block_editor(){ - wp_enqueue_style( 'edit-flow-block-custom-status-styles', EDIT_FLOW_URL . 'dist/custom-status.editor.build.css', false, EDIT_FLOW_VERSION ); - } + public function load_styles_for_block_editor() { + wp_enqueue_style( 'edit-flow-block-custom-status-styles', EDIT_FLOW_URL . 'dist/custom-status.editor.build.css', false, EDIT_FLOW_VERSION ); + } - /** - * Displays a notice to users if they have JS disabled - * Javascript is needed for custom statuses to be fully functional - */ - function no_js_notice() { - if( $this->is_whitelisted_page() ) : - ?> + /** + * Displays a notice to users if they have JS disabled + * Javascript is needed for custom statuses to be fully functional + */ + public function no_js_notice() { + if ( $this->is_whitelisted_page() ) : + ?> '; } - echo ''; } - - } - - // Load Javascript specific to the editorial metadata configuration view - if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { - wp_enqueue_script( 'jquery-ui-sortable' ); - wp_enqueue_script( 'edit-flow-editorial-metadata-configure', EDIT_FLOW_URL . 'modules/editorial-metadata/lib/editorial-metadata-configure.js', array( 'jquery', 'jquery-ui-sortable', 'edit-flow-settings-js' ), EDIT_FLOW_VERSION, true ); + + // Load Javascript specific to the editorial metadata configuration view + if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { + wp_enqueue_script( 'jquery-ui-sortable' ); + wp_enqueue_script( 'edit-flow-editorial-metadata-configure', EDIT_FLOW_URL . 'modules/editorial-metadata/lib/editorial-metadata-configure.js', array( 'jquery', 'jquery-ui-sortable', 'edit-flow-settings-js' ), EDIT_FLOW_VERSION, true ); + } } - } - - /** - * Register the post metadata taxonomy - */ - function register_taxonomy() { - - // We need to make sure taxonomy is registered for all of the post types that support it - $supported_post_types = $this->get_post_types_for_module( $this->module ); - - register_taxonomy( self::metadata_taxonomy, $supported_post_types, - array( - 'public' => false, - 'labels' => array( - 'name' => _x( 'Editorial Metadata', 'taxonomy general name', 'edit-flow' ), - 'singular_name' => _x( 'Editorial Metadata', 'taxonomy singular name', 'edit-flow' ), + + /** + * Register the post metadata taxonomy + */ + public function register_taxonomy() { + + // We need to make sure taxonomy is registered for all of the post types that support it + $supported_post_types = $this->get_post_types_for_module( $this->module ); + + register_taxonomy( self::metadata_taxonomy, $supported_post_types, + array( + 'public' => false, + 'labels' => array( + 'name' => _x( 'Editorial Metadata', 'taxonomy general name', 'edit-flow' ), + 'singular_name' => _x( 'Editorial Metadata', 'taxonomy singular name', 'edit-flow' ), 'search_items' => __( 'Search Editorial Metadata', 'edit-flow' ), 'popular_items' => __( 'Popular Editorial Metadata', 'edit-flow' ), 'all_items' => __( 'All Editorial Metadata', 'edit-flow' ), @@ -327,1114 +328,1203 @@ function register_taxonomy() { 'add_new_item' => __( 'Add New Editorial Metadata', 'edit-flow' ), 'new_item_name' => __( 'New Editorial Metadata', 'edit-flow' ), ), - 'rewrite' => false, - ) - ); - } - - /***************************************************** - * Post meta box generation and processing - ****************************************************/ - - /** - * Load the post metaboxes for all of the post types that are supported - */ - function handle_post_metaboxes() { - $title = __( 'Editorial Metadata', 'edit-flow' ); + 'rewrite' => false, + ) + ); + } - $supported_post_types = $this->get_post_types_for_module( $this->module ); - foreach ( $supported_post_types as $post_type ) { - add_meta_box( self::metadata_taxonomy, $title, array( $this, 'display_meta_box' ), $post_type, 'side' ); + /***************************************************** + * Post meta box generation and processing + ****************************************************/ + + /** + * Load the post metaboxes for all of the post types that are supported + */ + public function handle_post_metaboxes() { + $title = __( 'Editorial Metadata', 'edit-flow' ); + + $supported_post_types = $this->get_post_types_for_module( $this->module ); + foreach ( $supported_post_types as $post_type ) { + add_meta_box( self::metadata_taxonomy, $title, array( $this, 'display_meta_box' ), $post_type, 'side' ); + } } - } - - /** - * Displays HTML output for Editorial Metadata post meta box - * - * @param object $post Current post - */ - function display_meta_box( $post ) { - echo "
"; - // Add nonce for verification upon save - echo ""; - - if ( current_user_can( 'manage_options' ) ) { - // Make the metabox title include a link to edit the Editorial Metadata terms. Logic similar to how Core dashboard widgets work. - $url = add_query_arg( 'page', 'ef-editorial-metadata-settings', get_admin_url( null, 'admin.php' ) ); - echo '

' . __( 'Configure', 'edit-flow' ) . '

'; + + /** + * Displays HTML output for Editorial Metadata post meta box + * + * @param object $post Current post + */ + public function display_meta_box( $post ) { + echo "
"; + // Add nonce for verification upon save + echo ""; + + if ( current_user_can( 'manage_options' ) ) { + // Make the metabox title include a link to edit the Editorial Metadata terms. Logic similar to how Core dashboard widgets work. + $url = add_query_arg( 'page', 'ef-editorial-metadata-settings', get_admin_url( null, 'admin.php' ) ); + echo '

' . esc_html__( 'Configure', 'edit-flow' ) . '

'; + } + + $terms = $this->get_editorial_metadata_terms(); + if ( ! count( $terms ) ) { + $message = __( 'No editorial metadata available.' ); + if ( current_user_can( 'manage_options' ) ) { + /* translators: 1: The link to add editorial metadata fields */ + $message .= sprintf( __( ' Add fields to get started.' ), $this->get_link() ); + } else { + $message .= esc_html__( ' Encourage your site administrator to configure your editorial workflow by adding editorial metadata.' ); + } + echo '

' . wp_kses( $message, 'a' ) . '

'; + } else { + foreach ( $terms as $term ) { + $postmeta_key = $this->get_postmeta_key( $term ); + $current_metadata = esc_attr( $this->get_postmeta_value( $term, $post->ID ) ); + $type = $term->type; + $description = $term->description; + if ( $description ) { + $description_span = "$description"; + } else { + $description_span = ''; + } + // This is for the escaping of type. + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo "'; + echo "
"; + } // Done iterating through metadata terms + } + echo '
'; } - - $terms = $this->get_editorial_metadata_terms(); - if ( !count( $terms ) ) { - $message = __( 'No editorial metadata available.' ); - if ( current_user_can( 'manage_options' ) ) - $message .= sprintf( __( ' Add fields to get started.' ), $this->get_link() ); - else - $message .= __( ' Encourage your site administrator to configure your editorial workflow by adding editorial metadata.' ); - echo '

' . $message . '

'; - } else { + + /** + * Show date or datetime + * @param int $current_date + * @return string + * @since 0.8 + */ + private function show_date_or_datetime( $current_date ) { + + if ( gmdate( 'Hi', $current_date ) == '0000' ) { + return date_i18n( 'M d Y', $current_date ); + } else { + return date_i18n( 'M d Y H:i', $current_date ); + } + } + + /** + * Save any values in the editorial metadata post meta box + * + * @param int $id Unique ID for the post being saved + * @param object $post Post object + */ + public function save_meta_box( $id, $post ) { + + // Authentication checks: make sure data came from our meta box and that the current user is allowed to edit the post + // TODO: switch to using check_admin_referrer? See core (e.g. edit.php) for usage + if ( ! isset( $_POST[ self::metadata_taxonomy . '_nonce' ] ) + || ! wp_verify_nonce( $_POST[ self::metadata_taxonomy . '_nonce' ], 'ef-save-metabox' ) ) { + return $id; + } + + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) + || ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) + || ( 'post' == $post->post_type && ! current_user_can( 'edit_post', $id ) ) + || ( 'page' == $post->post_type && ! current_user_can( 'edit_page', $id ) ) ) { + return $id; + } + + // Authentication passed, let's save the data + $terms = $this->get_editorial_metadata_terms(); + $term_slugs = array(); + foreach ( $terms as $term ) { - $postmeta_key = $this->get_postmeta_key( $term ); - $current_metadata = esc_attr( $this->get_postmeta_value( $term, $post->ID ) ); + // Setup the key for this editorial metadata term (same as what's in $_POST) + $key = $this->get_postmeta_key( $term ); + + // Get the current editorial metadata + // TODO: do we care about the current_metadata at all? + //$current_metadata = get_post_meta( $id, $key, true ); + + $new_metadata = isset( $_POST[ $key ] ) ? $_POST[ $key ] : ''; + $type = $term->type; - $description = $term->description; - if ( $description ) - $description_span = "$description"; - else - $description_span = ''; - echo ""; - echo "
"; - } // Done iterating through metadata terms - } - echo "
"; - } - - /** - * Show date or datetime - * @param int $current_date - * @return string - * @since 0.8 - */ - private function show_date_or_datetime( $current_date ) { + do_action( 'ef_editorial_metadata_field_updated', $key, $new_metadata, $id, $type ); + } - if( date( 'Hi', $current_date ) == '0000') - return date_i18n( 'M d Y', $current_date ); - else - return date_i18n( 'M d Y H:i', $current_date ); - } + // Relate the post to the terms used and taxonomy type (wp_term_relationships table). + // This will allow us to update and display the count of metadata in posts in use per term. + // TODO: Core only correlates posts with terms if the post_status is publish. Do we care what it is? + if ( 'publish' === $post->post_status ) { + wp_set_object_terms( $id, $term_slugs, self::metadata_taxonomy ); + } + } - /** - * Save any values in the editorial metadata post meta box - * - * @param int $id Unique ID for the post being saved - * @param object $post Post object - */ - function save_meta_box( $id, $post ) { + /** + * Generate a unique key based on the term + * + * @param object $term Term object + * @return string $postmeta_key Unique key + */ + public function get_postmeta_key( $term ) { + $key = self::metadata_postmeta_key; + $type = $term->type; + $prefix = "{$key}_{$type}"; + $postmeta_key = "{$prefix}_" . ( is_object( $term ) ? $term->slug : $term ); + return $postmeta_key; + } - // Authentication checks: make sure data came from our meta box and that the current user is allowed to edit the post - // TODO: switch to using check_admin_referrer? See core (e.g. edit.php) for usage - if ( ! isset( $_POST[self::metadata_taxonomy . "_nonce"] ) - || ! wp_verify_nonce( $_POST[self::metadata_taxonomy . "_nonce"], 'ef-save-metabox' ) ) { - return $id; + /** + * Returns the value for the given metadata + * + * @param object|string|int term The term object, slug or ID for the metadata field term + * @param int post_id The ID of the post + */ + public function get_postmeta_value( $term, $post_id ) { + if ( ! is_object( $term ) ) { + if ( is_int( $term ) ) { + $term = $this->get_editorial_metadata_term_by( 'id', $term ); + } else { + $term = $this->get_editorial_metadata_term_by( 'slug', $term ); + } + } + $postmeta_key = $this->get_postmeta_key( $term ); + return get_metadata( 'post', $post_id, $postmeta_key, true ); } - - if( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) - || ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) - || $post->post_type == 'post' && !current_user_can( 'edit_post', $id ) - || $post->post_type == 'page' && !current_user_can( 'edit_page', $id ) ) { - return $id; + + /** + * Get all of the editorial metadata terms as objects and sort by position + * @todo Figure out what we should do with the filter... + * + * @param array $filter_args Filter to specific arguments + * @return array $ordered_terms The terms as they should be ordered + */ + public function get_editorial_metadata_terms( $filter_args = array() ) { + + // Try to fetch from internal object cache + $arg_hash = md5( serialize( $filter_args ) ); + if ( isset( $this->editorial_metadata_terms_cache[ $arg_hash ] ) ) { + return $this->editorial_metadata_terms_cache[ $arg_hash ]; + } + + $terms = get_terms( array( + 'taxonomy' => self::metadata_taxonomy, + 'orderby' => apply_filters( 'ef_editorial_metadata_term_order', 'name' ), + 'hide_empty' => false, + )); + + $ordered_terms = array(); + $hold_to_end = array(); + // Order the terms + foreach ( $terms as $key => $term ) { + + // Unencode and set all of our psuedo term meta because we need the position and viewable if they exists + // First do an array_merge() on the term object to make sure the keys exist, then array_merge() + // any values that may already exist + $unencoded_description = $this->get_unencoded_description( $term->description ); + $defaults = array( + 'description' => '', + 'viewable' => false, + 'position' => false, + ); + $term = array_merge( $defaults, (array) $term ); + if ( is_array( $unencoded_description ) ) { + $term = array_merge( $term, $unencoded_description ); + } + $term = (object) $term; + // We used to store the description field in a funny way + if ( isset( $term->desc ) ) { + $term->description = $term->desc; + unset( $term->desc ); + } + // Only add the term to the ordered array if it has a set position and doesn't conflict with another key + // Otherwise, hold it for later + if ( $term->position && ! array_key_exists( $term->position, $ordered_terms ) ) { + $ordered_terms[ (int) $term->position ] = $term; + } else { + $hold_to_end[] = $term; + } + } + // Sort the items numerically by key + ksort( $ordered_terms, SORT_NUMERIC ); + // Append all of the terms that didn't have an existing position + foreach ( $hold_to_end as $unpositioned_term ) { + $ordered_terms[] = $unpositioned_term; + } + + // If filter arguments were passed, do our filtering + $ordered_terms = wp_filter_object_list( $ordered_terms, $filter_args ); + + // Set the internal object cache + $this->editorial_metadata_terms_cache[ $arg_hash ] = $ordered_terms; + + return $ordered_terms; } - - // Authentication passed, let's save the data - $terms = $this->get_editorial_metadata_terms(); - $term_slugs = array(); - - foreach ( $terms as $term ) { - // Setup the key for this editorial metadata term (same as what's in $_POST) - $key = $this->get_postmeta_key( $term ); - - // Get the current editorial metadata - // TODO: do we care about the current_metadata at all? - //$current_metadata = get_post_meta( $id, $key, true ); - - $new_metadata = isset( $_POST[$key] ) ? $_POST[$key] : ''; - $type = $term->type; - if ( empty ( $new_metadata ) ) { - delete_post_meta( $id, $key ); - } else { + /** + * Returns a term for single metadata field + * + * @param int|string $field The slug or ID for the metadata field term to return + * @return object $term Term's object representation + */ + public function get_editorial_metadata_term_by( $field, $value ) { - // TODO: Move this to a function - if ( 'date' === $type ) { - $date_to_parse = isset( $_POST[ $key . '_hidden' ] ) ? $_POST[ $key . '_hidden' ] : ''; - $date = DateTime::createFromFormat('Y-m-d H:i', $date_to_parse ); + if ( ! in_array( $field, array( 'id', 'slug', 'name' ) ) ) { + return false; + } - if ( false !== $date ) { - $new_metadata = $date->getTimestamp(); - } else { - // Fallback, in case $_POST[ $key . '_hidden' ] was not previosuly set - $new_metadata = strtotime( $new_metadata ); - } + if ( 'id' == $field ) { + $field = 'term_id'; + } + + $terms = $this->get_editorial_metadata_terms(); + $term = wp_filter_object_list( $terms, array( $field => $value ) ); + + if ( ! empty( $term ) ) { + return array_shift( $term ); + } else { + return false; + } + } + + /** + * Register editorial metadata fields as columns in the manage posts view + * Only adds columns for the currently active post types - logic controlled in $this->init() + * + * @since 0.7 + * @uses apply_filters( 'manage_posts_columns' ) in wp-admin/includes/class-wp-posts-list-table.php + * + * @param array $posts_columns Existing post columns prepared by WP_List_Table + * @param array $posts_columns Previous post columns with the new values + */ + public function filter_manage_posts_columns( $posts_columns ) { + $screen = get_current_screen(); + if ( $screen ) { + add_filter( "manage_{$screen->id}_sortable_columns", array( $this, 'filter_manage_posts_sortable_columns' ) ); + $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); + foreach ( $terms as $term ) { + // Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions + $key = $this->module->slug . '-' . $term->slug; + $posts_columns[ $key ] = $term->name; } - if ( 'number' === $type ) { - $new_metadata = (int)$new_metadata; + } + return $posts_columns; + } + + /** + * Register any viewable date editorial metadata as a sortable column + * + * @since 0.7.4 + * + * @param array $sortable_columns Any existing sortable columns (e.g. Title) + * @return array $sortable_columms Sortable columns with editorial metadata date fields added + */ + public function filter_manage_posts_sortable_columns( $sortable_columns ) { + + $terms = $this->get_editorial_metadata_terms( array( + 'viewable' => true, + 'type' => 'date', + ) ); + foreach ( $terms as $term ) { + // Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions + $key = $this->module->slug . '-' . $term->slug; + $sortable_columns[ $key ] = $key; + } + return $sortable_columns; + } + + /** + * If we're ordering by a sortable column, let's modify the query + * + * @since 0.7.4 + */ + public function action_parse_query( $query ) { + + if ( is_admin() && false !== stripos( get_query_var( 'orderby' ), $this->module->slug ) ) { + $term_slug = sanitize_key( str_replace( $this->module->slug . '-', '', get_query_var( 'orderby' ) ) ); + $term = $this->get_editorial_metadata_term_by( 'slug', $term_slug ); + $meta_key = $this->get_postmeta_key( $term ); + set_query_var( 'meta_key', $meta_key ); + set_query_var( 'orderby', 'meta_value_num' ); + } + } + + /** + * Handle the output of an editorial metadata custom column + * Logic for the post types this is called on is controlled in $this->init() + * + * @since 0.7 + * @uses do_action( 'manage_posts_custom_column' ) in wp-admin/includes/class-wp-posts-list-table.php + * + * @param string $column_name Unique string for the column + * @param int $post_id ID for the post of the row + */ + public function action_manage_posts_custom_column( $column_name, $post_id ) { + + $terms = $this->get_editorial_metadata_terms(); + // We're looking for the proper term to display its saved value + foreach ( $terms as $term ) { + $key = $this->module->slug . '-' . $term->slug; + if ( $column_name != $key ) { + continue; } - - $new_metadata = strip_tags( $new_metadata ); - update_post_meta( $id, $key, $new_metadata ); - - // Add the slugs of the terms with non-empty new metadata to an array - $term_slugs[] = $term->slug; - } - do_action( 'ef_editorial_metadata_field_updated', $key, $new_metadata, $id, $type ); + + $current_metadata = $this->get_postmeta_value( $term, $post_id ); + echo esc_html( $this->generate_editorial_metadata_term_output( $term, $current_metadata ) ); + } } - - // Relate the post to the terms used and taxonomy type (wp_term_relationships table). - // This will allow us to update and display the count of metadata in posts in use per term. - // TODO: Core only correlates posts with terms if the post_status is publish. Do we care what it is? - if ( $post->post_status === 'publish' ) { - wp_set_object_terms( $id, $term_slugs, self::metadata_taxonomy ); + + /** + * If the Edit Flow Calendar is enabled, add viewable Editorial Metadata terms + * + * @since 0.7 + * @uses apply_filters( 'ef_calendar_item_information_fields' ) + * + * @param array $calendar_fields Additional data fields to include on the calendar + * @param int $post_id Unique ID for the post data we're building + * @return array $calendar_fields Calendar fields with our viewable Editorial Metadata added + */ + public function filter_calendar_item_fields( $calendar_fields, $post_id ) { + + + // Make sure we respect which post type we're on + if ( ! in_array( get_post_type( $post_id ), $this->get_post_types_for_module( $this->module ) ) ) { + return $calendar_fields; + } + + $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); + + foreach ( $terms as $term ) { + $key = $this->module->slug . '-' . $term->slug; + + // Default values + $current_metadata = $this->get_postmeta_value( $term, $post_id ); + $term_data = array( + 'label' => $term->name, + 'value' => $this->generate_editorial_metadata_term_output( $term, $current_metadata ), + ); + $term_data['editable'] = true; + $term_data['type'] = $term->type; + $calendar_fields[ $key ] = $term_data; + } + return $calendar_fields; } - } - - /** - * Generate a unique key based on the term - * - * @param object $term Term object - * @return string $postmeta_key Unique key - */ - function get_postmeta_key( $term ) { - $key = self::metadata_postmeta_key; - $type = $term->type; - $prefix = "{$key}_{$type}"; - $postmeta_key = "{$prefix}_" . ( is_object( $term ) ? $term->slug : $term ); - return $postmeta_key; - } - - /** - * Returns the value for the given metadata - * - * @param object|string|int term The term object, slug or ID for the metadata field term - * @param int post_id The ID of the post - */ - function get_postmeta_value( $term, $post_id ) { - if( ! is_object( $term ) ) { - if ( is_int( $term ) ) - $term = $this->get_editorial_metadata_term_by( 'id', $term ); - else - $term = $this->get_editorial_metadata_term_by( 'slug', $term ); + + /** + * If the Edit Flow Story Budget is enabled, register our viewable terms as columns + * + * @since 0.7 + * @uses apply_filters( 'ef_story_budget_term_columns' ) + * + * @param array $term_columns The existing columns on the story budget + * @return array $term_columns Term columns with viewable Editorial Metadata terms + */ + public function filter_story_budget_term_columns( $term_columns ) { + + $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); + foreach ( $terms as $term ) { + // Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions + $key = $this->module->slug . '-' . $term->slug; + // Switch to underscores + $key = str_replace( '-', '_', $key ); + $term_columns[ $key ] = $term->name; + } + return $term_columns; } - $postmeta_key = $this->get_postmeta_key( $term ); - return get_metadata( 'post', $post_id, $postmeta_key, true ); - } - - /** - * Get all of the editorial metadata terms as objects and sort by position - * @todo Figure out what we should do with the filter... - * - * @param array $filter_args Filter to specific arguments - * @return array $ordered_terms The terms as they should be ordered - */ - function get_editorial_metadata_terms( $filter_args = array() ) { - - // Try to fetch from internal object cache - $arg_hash = md5( serialize( $filter_args ) ); - if ( isset( $this->editorial_metadata_terms_cache[ $arg_hash ] ) ) { - return $this->editorial_metadata_terms_cache[ $arg_hash ]; + + /** + * If the Edit Flow Story Budget is enabled, + * + * @since 0.7 + * @uses apply_filters( 'ef_story_budget_term_column_value' ) + * + * @param object $post The post we're displaying + * @param string $column_name Name of the column, as registered with EF_Story_Budget::register_term_columns + * @param object $parent_term The parent term for the term column + */ + public function filter_story_budget_term_column_values( $column_name, $post, $parent_term ) { + + $local_column_name = str_replace( '_', '-', $column_name ); + // Don't accidentally handle values not our own + if ( false === strpos( $local_column_name, $this->module->slug ) ) { + return $column_name; + } + + $term_slug = str_replace( $this->module->slug . '-', '', $local_column_name ); + $term = $this->get_editorial_metadata_term_by( 'slug', $term_slug ); + + // Don't allow non-viewable term data to be displayed + if ( ! $term->viewable ) { + return $column_name; + } + + $current_metadata = $this->get_postmeta_value( $term, $post->ID ); + $output = $this->generate_editorial_metadata_term_output( $term, $current_metadata ); + + return $output; + } + + /** + * Generate the presentational output for an editorial metadata term + * + * @since 0.8 + * + * @param object $term The editorial metadata term + * @return string $html How the term should be rendered + */ + private function generate_editorial_metadata_term_output( $term, $pm_value ) { + + $output = ''; + switch ( $term->type ) { + case 'date': + if ( empty( $pm_value ) ) { + break; + } + + // All day vs. day and time + $date = gmdate( get_option( 'date_format' ), $pm_value ); + $time = gmdate( get_option( 'time_format' ), $pm_value ); + if ( '0000' == gmdate( 'Hi', $pm_value ) ) { + $pm_value = $date; + } else { + // translators: 1: date, 2: time + $pm_value = sprintf( __( '%1$s at %2$s', 'edit-flow' ), $date, $time ); + } + $output = esc_html( $pm_value ); + break; + case 'location': + case 'text': + case 'number': + case 'paragraph': + if ( $pm_value ) { + $output = esc_html( $pm_value ); + } + break; + case 'checkbox': + if ( $pm_value ) { + $output = __( 'Yes', 'edit-flow' ); + } else { + $output = __( 'No', 'edit-flow' ); + } + break; + case 'user': + if ( empty( $pm_value ) ) { + break; + } + $userdata = get_user_by( 'id', $pm_value ); + if ( is_object( $userdata ) ) { + $output = esc_html( $userdata->display_name ); + } + break; + default: + break; + } + return $output; } - - $args = array( - 'orderby' => apply_filters( 'ef_editorial_metadata_term_order', 'name' ), - 'hide_empty' => false + + /** + * Update an existing editorial metadata term if the term_id exists + * + * @since 0.7 + * + * @param int $term_id The term's unique ID + * @param array $args Any values that need to be updated for the term + * @return object|WP_Error $updated_term The updated term or a WP_Error object if something disastrous happened + */ + public function update_editorial_metadata_term( $term_id, $args ) { + + $new_args = array(); + $old_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); + if ( $old_term ) { + $old_args = array( + 'position' => $old_term->position, + 'name' => $old_term->name, + 'slug' => $old_term->slug, + 'description' => $old_term->description, + 'type' => $old_term->type, + 'viewable' => $old_term->viewable, + ); + } + $new_args = array_merge( $old_args, $args ); + + // We're encoding metadata that isn't supported by default in the term's description field + $args_to_encode = array( + 'description' => $new_args['description'], + 'position' => $new_args['position'], + 'type' => $new_args['type'], + 'viewable' => $new_args['viewable'], ); + $encoded_description = $this->get_encoded_description( $args_to_encode ); + $new_args['description'] = $encoded_description; + + $updated_term = wp_update_term( $term_id, self::metadata_taxonomy, $new_args ); + + // Reset the internal object cache + $this->editorial_metadata_terms_cache = array(); - $terms = get_terms( self::metadata_taxonomy, $args ); - $ordered_terms = array(); - $hold_to_end = array(); - // Order the terms - foreach ( $terms as $key => $term ) { - - // Unencode and set all of our psuedo term meta because we need the position and viewable if they exists - // First do an array_merge() on the term object to make sure the keys exist, then array_merge() - // any values that may already exist - $unencoded_description = $this->get_unencoded_description( $term->description ); + $updated_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); + return $updated_term; + } + + /** + * Insert a new editorial metadata term + * @todo Handle conflicts with existing terms at that position (if relevant) + * + * @since 0.7 + */ + public function insert_editorial_metadata_term( $args ) { + + + // Term is always added to the end of the list + $default_position = count( $this->get_editorial_metadata_terms() ) + 2; $defaults = array( + 'position' => $default_position, + 'name' => '', + 'slug' => '', 'description' => '', + 'type' => '', 'viewable' => false, - 'position' => false, ); - $term = array_merge( $defaults, (array)$term ); - if ( is_array( $unencoded_description ) ) { - $term = array_merge( $term, $unencoded_description ); - } - $term = (object)$term; - // We used to store the description field in a funny way - if ( isset( $term->desc ) ) { - $term->description = $term->desc; - unset( $term->desc ); - } - // Only add the term to the ordered array if it has a set position and doesn't conflict with another key - // Otherwise, hold it for later - if ( $term->position && !array_key_exists( $term->position, $ordered_terms ) ) - $ordered_terms[(int)$term->position] = $term; - else - $hold_to_end[] = $term; + $args = array_merge( $defaults, $args ); + $term_name = $args['name']; + unset( $args['name'] ); + + // We're encoding metadata that isn't supported by default in the term's description field + $args_to_encode = array( + 'description' => $args['description'], + 'position' => $args['position'], + 'type' => $args['type'], + 'viewable' => $args['viewable'], + ); + $encoded_description = $this->get_encoded_description( $args_to_encode ); + $args['description'] = $encoded_description; + + $inserted_term = wp_insert_term( $term_name, self::metadata_taxonomy, $args ); + + // Reset the internal object cache + $this->editorial_metadata_terms_cache = array(); + + return $inserted_term; + } + + /** + * Settings and other management code + */ + + /** + * Delete an existing editorial metadata term + * + * @since 0.7 + * + * @param int $term_id The term we want deleted + * @return bool $result Whether or not the term was deleted + */ + public function delete_editorial_metadata_term( $term_id ) { + $result = wp_delete_term( $term_id, self::metadata_taxonomy ); + + // Reset the internal object cache + $this->editorial_metadata_terms_cache = array(); + + return $result; + } + + /** + * Generate a link to one of the editorial metadata actions + * + * @since 0.7 + * + * @param array $args (optional) Action and any query args to add to the URL + * @return string $link Direct link to complete the action + */ + public function get_link( $args = array() ) { + if ( ! isset( $args['action'] ) ) { + $args['action'] = ''; + } + if ( ! isset( $args['page'] ) ) { + $args['page'] = $this->module->settings_slug; + } + // Add other things we may need depending on the action + switch ( $args['action'] ) { + case 'make-viewable': + case 'make-hidden': + case 'delete-term': + $args['nonce'] = wp_create_nonce( $args['action'] ); + break; + default: + break; + } + return add_query_arg( $args, get_admin_url( null, 'admin.php' ) ); } - // Sort the items numerically by key - ksort( $ordered_terms, SORT_NUMERIC ); - // Append all of the terms that didn't have an existing position - foreach( $hold_to_end as $unpositioned_term ) - $ordered_terms[] = $unpositioned_term; - // If filter arguments were passed, do our filtering - $ordered_terms = wp_filter_object_list( $ordered_terms, $filter_args ); + /** + * Handles a request to add a new piece of editorial metadata + */ + public function handle_add_editorial_metadata() { + + if ( ! isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) + || $_GET['page'] != $this->module->settings_slug || 'add-term' != $_POST['form-action'] ) { + return; + } + + if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'editorial-metadata-add-nonce' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + // Sanitize all of the user-entered values + $term_name = isset( $_POST['metadata_name'] ) ? sanitize_text_field( trim( $_POST['metadata_name'] ) ) : ''; + $term_slug = ( ! empty( $_POST['metadata_slug'] ) ) ? sanitize_title( $_POST['metadata_slug'] ) : sanitize_title( $term_name ); + $term_description = isset( $_POST['metadata_description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['metadata_description'] ) ) ) : ''; + $term_type = isset( $_POST['metadata_type'] ) ? sanitize_key( $_POST['metadata_type'] ) : ''; + + $_REQUEST['form-errors'] = array(); + + /** + * Form validation for adding new editorial metadata term + * + * Details + * - "name", "slug", and "type" are required fields + * - "description" can accept a limited amount of HTML, and is optional + */ + // Field is required + if ( empty( $term_name ) ) { + $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the editorial metadata.', 'edit-flow' ); + } + // Field is required + if ( empty( $term_slug ) ) { + $_REQUEST['form-errors']['slug'] = __( 'Please enter a slug for the editorial metadata.', 'edit-flow' ); + } + if ( term_exists( $term_slug ) ) { + $_REQUEST['form-errors']['name'] = __( 'Name conflicts with existing term. Please choose another.', 'edit-flow' ); + } + // Check to ensure a term with the same name doesn't exist + if ( $this->get_editorial_metadata_term_by( 'name', $term_name, self::metadata_taxonomy ) ) { + $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); + } + // Check to ensure a term with the same slug doesn't exist + if ( $this->get_editorial_metadata_term_by( 'slug', $term_slug ) ) { + $_REQUEST['form-errors']['slug'] = __( 'Slug already in use. Please choose another.', 'edit-flow' ); + } + // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug + // Check that the term name doesn't exceed 200 chars + if ( strlen( $term_name ) > 200 ) { + $_REQUEST['form-errors']['name'] = __( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ); + } + // Metadata type needs to pass our whitelist check + $metadata_types = $this->get_supported_metadata_types(); + if ( empty( $_POST['metadata_type'] ) || ! isset( $metadata_types[ $_POST['metadata_type'] ] ) ) { + $_REQUEST['form-errors']['type'] = __( 'Please select a valid metadata type.', 'edit-flow' ); + } + // Metadata viewable needs to be a valid Yes or No + $term_viewable = false; + if ( isset( $_POST['metadata_viewable'] ) && 'yes' == $_POST['metadata_viewable'] ) { + $term_viewable = true; + } + + // Kick out if there are any errors + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + if ( count( $_REQUEST['form-errors'] ) ) { + $_REQUEST['error'] = 'form-error'; + return; + } + + // Try to add the status + $args = array( + 'name' => $term_name, + 'description' => $term_description, + 'slug' => $term_slug, + 'type' => $term_type, + 'viewable' => $term_viewable, + ); + $return = $this->insert_editorial_metadata_term( $args ); + if ( is_wp_error( $return ) ) { + wp_die( esc_html__( 'Error adding term.', 'edit-flow' ) ); + } + + $redirect_url = add_query_arg( array( + 'page' => $this->module->settings_slug, + 'message' => 'term-added', + ), get_admin_url( null, 'admin.php' ) ); + wp_redirect( $redirect_url ); + exit; + } + + /** + * Handles a request to edit an editorial metadata + */ + public function handle_edit_editorial_metadata() { + if ( ! isset( $_POST['submit'], $_GET['page'], $_GET['action'], $_GET['term-id'] ) + || $_GET['page'] != $this->module->settings_slug || 'edit-term' != $_GET['action'] ) { + return; + } + + if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'editorial-metadata-edit-nonce' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + if ( ! $existing_term = $this->get_editorial_metadata_term_by( 'id', (int) $_GET['term-id'] ) ) { + wp_die( esc_html( $this->module->messages['term-missing'] ) ); + } + + $new_name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; + $denew_descriptionscription = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; + + /** + * Form validation for editing editorial metadata term + * + * Details + * - "name", "slug", and "type" are required fields + * - "description" can accept a limited amount of HTML, and is optional + */ + $_REQUEST['form-errors'] = array(); + // Check if name field was filled in + if ( empty( $new_name ) ) { + $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the editorial metadata', 'edit-flow' ); + } + + // Check that the name isn't numeric + if ( is_numeric( $new_name ) ) { + $_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the editorial metadata.', 'edit-flow' ); + } + + $term_exists = term_exists( sanitize_title( $new_name ) ); + if ( $term_exists && $term_exists != $existing_term->term_id ) { + $_REQUEST['form-errors']['name'] = __( 'Metadata name conflicts with existing term. Please choose another.', 'edit-flow' ); + } + + // Check to ensure a term with the same name doesn't exist, + $search_term = $this->get_editorial_metadata_term_by( 'name', $new_name ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { + $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); + } + // or that the term name doesn't map to an existing term's slug + $search_term = $this->get_editorial_metadata_term_by( 'slug', sanitize_title( $new_name ) ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { + $_REQUEST['form-errors']['name'] = __( 'Name conflicts with slug for another term. Please choose something else.', 'edit-flow' ); + } + + // Check that the term name doesn't exceed 200 chars + if ( strlen( $new_name ) > 200 ) { + $_REQUEST['form-errors']['name'] = __( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ); + } + // Make sure the viewable state is valid + $new_viewable = false; + if ( isset( $_POST['viewable'] ) && 'yes' == $_POST['viewable'] ) { + $new_viewable = true; + } + + // Kick out if there are any errors + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + if ( count( $_REQUEST['form-errors'] ) ) { + $_REQUEST['error'] = 'form-error'; + return; + } + + // Try to add the metadata term + $args = array( + 'name' => $new_name, + 'description' => $new_description, + 'viewable' => $new_viewable, + ); + $return = $this->update_editorial_metadata_term( $existing_term->term_id, $args ); + if ( is_wp_error( $return ) ) { + wp_die( esc_html__( 'Error updating term.', 'edit-flow' ) ); + } + + $redirect_url = add_query_arg( array( + 'page' => $this->module->settings_slug, + 'message' => 'term-updated', + ), get_admin_url( null, 'admin.php' ) ); + wp_redirect( $redirect_url ); + exit; + } + + /** + * Handle a $_GET request to change the visibility of an Editorial Metadata term + * + * @since 0.7 + */ + public function handle_change_editorial_metadata_visibility() { + + // Check that the current GET request is our GET request + if ( ! isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] ) + || $_GET['page'] != $this->module->settings_slug || ! in_array( $_GET['action'], array( 'make-viewable', 'make-hidden' ) ) ) { + return; + } + + // Check for proper nonce + if ( ! isset( $_GET['nonce'] ) || ( ! wp_verify_nonce( $_GET['nonce'], 'make-viewable' ) && ! wp_verify_nonce( $_GET['nonce'], 'make-hidden' ) ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + // Only allow users with the proper caps + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + $term_id = (int) $_GET['term-id']; + $args = array(); + if ( 'make-viewable' == $_GET['action'] ) { + $args['viewable'] = true; + } elseif ( 'make-hidden' == $_GET['action'] ) { + $args['viewable'] = false; + } + + $return = $this->update_editorial_metadata_term( $term_id, $args ); + if ( is_wp_error( $return ) ) { + wp_die( esc_html__( 'Error updating term.', 'edit-flow' ) ); + } + + $redirect_url = $this->get_link( array( 'message' => 'term-visibility-changed' ) ); + wp_redirect( $redirect_url ); + exit; + } + + /** + * Handle the request to update a given Editorial Metadata term via inline edit + * + * @since 0.7 + */ + public function handle_ajax_inline_save_term() { + + if ( ! isset( $_POST['inline_edit'] ) || ! wp_verify_nonce( $_POST['inline_edit'], 'editorial-metadata-inline-edit-nonce' ) ) { + die( esc_html( $this->module->messages['nonce-failed'] ) ); + } - // Set the internal object cache - $this->editorial_metadata_terms_cache[ $arg_hash ] = $ordered_terms; + if ( ! current_user_can( 'manage_options' ) ) { + die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } - return $ordered_terms; - } - - /** - * Returns a term for single metadata field - * - * @param int|string $field The slug or ID for the metadata field term to return - * @return object $term Term's object representation - */ - function get_editorial_metadata_term_by( $field, $value ) { + $term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : 0; + if ( ! $existing_term = $this->get_editorial_metadata_term_by( 'id', $term_id ) ) { + die( esc_html( $this->module->messages['term-missing'] ) ); + } - if ( ! in_array( $field, array( 'id', 'slug', 'name' ) ) ) - return false; + $metadata_name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; + $metadata_description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; - if ( 'id' == $field ) - $field = 'term_id'; + /** + * Form validation for editing editorial metadata term + */ + // Check if name field was filled in + if ( empty( $metadata_name ) ) { + $change_error = new WP_Error( 'invalid', _esc_html__( 'Please enter a name for the editorial metadata', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } - $terms = $this->get_editorial_metadata_terms(); - $term = wp_filter_object_list( $terms, array( $field => $value ) ); + // Check that the name isn't numeric + if ( is_numeric( $metadata_name ) ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Please enter a valid, non-numeric name for the editorial metadata.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } - if ( ! empty( $term ) ) - return array_shift( $term ); - else - return false; - } - - /** - * Register editorial metadata fields as columns in the manage posts view - * Only adds columns for the currently active post types - logic controlled in $this->init() - * - * @since 0.7 - * @uses apply_filters( 'manage_posts_columns' ) in wp-admin/includes/class-wp-posts-list-table.php - * - * @param array $posts_columns Existing post columns prepared by WP_List_Table - * @param array $posts_columns Previous post columns with the new values - */ - function filter_manage_posts_columns( $posts_columns ) { - $screen = get_current_screen(); - if ( $screen ) { - add_filter( "manage_{$screen->id}_sortable_columns", array( $this, 'filter_manage_posts_sortable_columns' ) ); - $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); - foreach( $terms as $term ) { - // Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions - $key = $this->module->slug . '-' . $term->slug; - $posts_columns[$key] = $term->name; + // Check that the term name doesn't exceed 200 chars + if ( strlen( $metadata_name ) > 200 ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Name cannot exceed 200 characters. Please try a shorter name.' ) ); + die( esc_html( $change_error->get_error_message() ) ); } - } - return $posts_columns; - } - - /** - * Register any viewable date editorial metadata as a sortable column - * - * @since 0.7.4 - * - * @param array $sortable_columns Any existing sortable columns (e.g. Title) - * @return array $sortable_columms Sortable columns with editorial metadata date fields added - */ - function filter_manage_posts_sortable_columns( $sortable_columns ) { - $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true, 'type' => 'date' ) ); - foreach( $terms as $term ) { - // Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions - $key = $this->module->slug . '-' . $term->slug; - $sortable_columns[$key] = $key; - } - return $sortable_columns; - } + // 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( sanitize_title( $metadata_name ) ); + if ( $term_exists && $term_exists != $term_id ) { + $change_error = new WP_Error( 'invalid', esc_html____( 'Metadata name conflicts with existing term. Please choose another.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } - /** - * If we're ordering by a sortable column, let's modify the query - * - * @since 0.7.4 - */ - function action_parse_query( $query ) { + // Check to ensure a term with the same name doesn't exist, + $search_term = $this->get_editorial_metadata_term_by( 'name', $metadata_name ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Name already in use. Please choose another.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } - if ( is_admin() && false !== stripos( get_query_var( 'orderby' ), $this->module->slug ) ) { - $term_slug = sanitize_key( str_replace( $this->module->slug . '-', '', get_query_var( 'orderby') ) ); - $term = $this->get_editorial_metadata_term_by( 'slug', $term_slug ); - $meta_key = $this->get_postmeta_key( $term ); - set_query_var( 'meta_key', $meta_key ); - set_query_var( 'orderby', 'meta_value_num' ); - } - } - - /** - * Handle the output of an editorial metadata custom column - * Logic for the post types this is called on is controlled in $this->init() - * - * @since 0.7 - * @uses do_action( 'manage_posts_custom_column' ) in wp-admin/includes/class-wp-posts-list-table.php - * - * @param string $column_name Unique string for the column - * @param int $post_id ID for the post of the row - */ - function action_manage_posts_custom_column( $column_name, $post_id ) { - - $terms = $this->get_editorial_metadata_terms(); - // We're looking for the proper term to display its saved value - foreach( $terms as $term ) { - $key = $this->module->slug . '-' . $term->slug; - if ( $column_name != $key ) - continue; - - $current_metadata = $this->get_postmeta_value( $term, $post_id ); - echo $this->generate_editorial_metadata_term_output( $term, $current_metadata ); - } - - } - - /** - * If the Edit Flow Calendar is enabled, add viewable Editorial Metadata terms - * - * @since 0.7 - * @uses apply_filters( 'ef_calendar_item_information_fields' ) - * - * @param array $calendar_fields Additional data fields to include on the calendar - * @param int $post_id Unique ID for the post data we're building - * @return array $calendar_fields Calendar fields with our viewable Editorial Metadata added - */ - function filter_calendar_item_fields( $calendar_fields, $post_id ) { + // or that the term name doesn't map to an existing term's slug + $search_term = $this->get_editorial_metadata_term_by( 'slug', sanitize_title( $metadata_name ) ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } - - // Make sure we respect which post type we're on - if ( !in_array( get_post_type( $post_id ), $this->get_post_types_for_module( $this->module ) ) ) - return $calendar_fields; - - $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); - - foreach( $terms as $term ) { - $key = $this->module->slug . '-' . $term->slug; - - // Default values - $current_metadata = $this->get_postmeta_value( $term, $post_id ); - $term_data = array( - 'label' => $term->name, - 'value' => $this->generate_editorial_metadata_term_output( $term, $current_metadata ), + // Prepare the term name and description for saving + $args = array( + 'name' => $metadata_name, + 'description' => $metadata_description, ); - $term_data['editable'] = true; - $term_data['type'] = $term->type; - $calendar_fields[$key] = $term_data; + $return = $this->update_editorial_metadata_term( $existing_term->term_id, $args ); + if ( ! is_wp_error( $return ) ) { + set_current_screen( 'edit-editorial-metadata' ); + $wp_list_table = new EF_Editorial_Metadata_List_Table(); + $wp_list_table->prepare_items(); + echo wp_kses_post( $wp_list_table->single_row( $return ) ); + die(); + } else { + /* Translators: 1: the name of the term that could not be found */ + $change_error = new WP_Error( 'invalid', wp_kses( sprintf( __( 'Could not update the term: %s', 'edit-flow' ), $metadata_name ), 'strong' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } } - return $calendar_fields; - - } - - /** - * If the Edit Flow Story Budget is enabled, register our viewable terms as columns - * - * @since 0.7 - * @uses apply_filters( 'ef_story_budget_term_columns' ) - * - * @param array $term_columns The existing columns on the story budget - * @return array $term_columns Term columns with viewable Editorial Metadata terms - */ - function filter_story_budget_term_columns( $term_columns ) { - - $terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); - foreach( $terms as $term ) { - // Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions - $key = $this->module->slug . '-' . $term->slug; - // Switch to underscores - $key = str_replace( '-', '_', $key ); - $term_columns[$key] = $term->name; - } - return $term_columns; - - } - - /** - * If the Edit Flow Story Budget is enabled, - * - * @since 0.7 - * @uses apply_filters( 'ef_story_budget_term_column_value' ) - * - * @param object $post The post we're displaying - * @param string $column_name Name of the column, as registered with EF_Story_Budget::register_term_columns - * @param object $parent_term The parent term for the term column - */ - function filter_story_budget_term_column_values( $column_name, $post, $parent_term ) { - - $local_column_name = str_replace( '_', '-', $column_name ); - // Don't accidentally handle values not our own - if ( false === strpos( $local_column_name, $this->module->slug ) ) - return $column_name; - - $term_slug = str_replace( $this->module->slug . '-', '', $local_column_name ); - $term = $this->get_editorial_metadata_term_by( 'slug', $term_slug ); - - // Don't allow non-viewable term data to be displayed - if ( !$term->viewable ) - return $column_name; - - $current_metadata = $this->get_postmeta_value( $term, $post->ID ); - $output = $this->generate_editorial_metadata_term_output( $term, $current_metadata ); - - return $output; - } - /** - * Generate the presentational output for an editorial metadata term - * - * @since 0.8 - * - * @param object $term The editorial metadata term - * @return string $html How the term should be rendered - */ - private function generate_editorial_metadata_term_output( $term, $pm_value ) { + /** + * Handle the ajax request to update all of the term positions + * + * @since 0.7 + */ + public function handle_ajax_update_term_positions() { - $output = ''; - switch( $term->type ) { - case "date": - if ( empty( $pm_value ) ) - break; + if ( ! isset( $_POST['editorial_metadata_sortable_nonce'] ) || ! wp_verify_nonce( $_POST['editorial_metadata_sortable_nonce'], 'editorial-metadata-sortable' ) ) { + $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] ); + } - // All day vs. day and time - $date = date( get_option( 'date_format' ), $pm_value ); - $time = date( get_option( 'time_format' ), $pm_value ); - if( date( 'Hi', $pm_value ) == '0000' ) - $pm_value = $date; - else - $pm_value = sprintf( __( '%1$s at %2$s', 'edit-flow' ), $date, $time ); - $output = esc_html( $pm_value ); - break; - case "location": - case "text": - case "number": - case "paragraph": - if ( $pm_value ) - $output = esc_html( $pm_value ); - break; - case "checkbox": - if ( $pm_value ) - $output = __( 'Yes', 'edit-flow' ); - else - $output = __( 'No', 'edit-flow' ); - break; - case "user": - if ( empty( $pm_value ) ) - break; - $userdata = get_user_by( 'id', $pm_value ); - if ( is_object( $userdata ) ) - $output = esc_html( $userdata->display_name ); - break; - default: - break; - } - return $output; - } - - /** - * Update an existing editorial metadata term if the term_id exists - * - * @since 0.7 - * - * @param int $term_id The term's unique ID - * @param array $args Any values that need to be updated for the term - * @return object|WP_Error $updated_term The updated term or a WP_Error object if something disastrous happened - */ - function update_editorial_metadata_term( $term_id, $args ) { - - $new_args = array(); - $old_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); - if ( $old_term ) - $old_args = array( - 'position' => $old_term->position, - 'name' => $old_term->name, - 'slug' => $old_term->slug, - 'description' => $old_term->description, - 'type' => $old_term->type, - 'viewable' => $old_term->viewable, - ); - $new_args = array_merge( $old_args, $args ); - - // We're encoding metadata that isn't supported by default in the term's description field - $args_to_encode = array( - 'description' => $new_args['description'], - 'position' => $new_args['position'], - 'type' => $new_args['type'], - 'viewable' => $new_args['viewable'], - ); - $encoded_description = $this->get_encoded_description( $args_to_encode ); - $new_args['description'] = $encoded_description; - - $updated_term = wp_update_term( $term_id, self::metadata_taxonomy, $new_args ); - - // Reset the internal object cache - $this->editorial_metadata_terms_cache = array(); - - $updated_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); - return $updated_term; - } - - /** - * Insert a new editorial metadata term - * @todo Handle conflicts with existing terms at that position (if relevant) - * - * @since 0.7 - */ - function insert_editorial_metadata_term( $args ) { - - - // Term is always added to the end of the list - $default_position = count( $this->get_editorial_metadata_terms() ) + 2; - $defaults = array( - 'position' => $default_position, - 'name' => '', - 'slug' => '', - 'description' => '', - 'type' => '', - 'viewable' => false, - ); - $args = array_merge( $defaults, $args ); - $term_name = $args['name']; - unset( $args['name'] ); - - // We're encoding metadata that isn't supported by default in the term's description field - $args_to_encode = array( - 'description' => $args['description'], - 'position' => $args['position'], - 'type' => $args['type'], - 'viewable' => $args['viewable'], - ); - $encoded_description = $this->get_encoded_description( $args_to_encode ); - $args['description'] = $encoded_description; - - $inserted_term = wp_insert_term( $term_name, self::metadata_taxonomy, $args ); - - // Reset the internal object cache - $this->editorial_metadata_terms_cache = array(); - - return $inserted_term; - } - - /** - * Settings and other management code - */ - - /** - * Delete an existing editorial metadata term - * - * @since 0.7 - * - * @param int $term_id The term we want deleted - * @return bool $result Whether or not the term was deleted - */ - function delete_editorial_metadata_term( $term_id ) { - $result = wp_delete_term( $term_id, self::metadata_taxonomy ); + if ( ! current_user_can( 'manage_options' ) ) { + $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] ); + } - // Reset the internal object cache - $this->editorial_metadata_terms_cache = array(); + if ( ! isset( $_POST['term_positions'] ) || ! is_array( $_POST['term_positions'] ) ) { + $this->print_ajax_response( 'error', __( 'Terms not set.', 'edit-flow' ) ); + } - return $result; - } - - /** - * Generate a link to one of the editorial metadata actions - * - * @since 0.7 - * - * @param array $args (optional) Action and any query args to add to the URL - * @return string $link Direct link to complete the action - */ - function get_link( $args = array() ) { - if ( !isset( $args['action'] ) ) - $args['action'] = ''; - if ( !isset( $args['page'] ) ) - $args['page'] = $this->module->settings_slug; - // Add other things we may need depending on the action - switch( $args['action'] ) { - case 'make-viewable': - case 'make-hidden': - case 'delete-term': - $args['nonce'] = wp_create_nonce( $args['action'] ); - break; - default: - break; + foreach ( $_POST['term_positions'] as $position => $term_id ) { + + // Have to add 1 to the position because the index started with zero + $args = array( + 'position' => (int) $position + 1, + ); + $return = $this->update_editorial_metadata_term( (int) $term_id, $args ); + // @todo check that this was a valid return + } + $this->print_ajax_response( 'success', $this->module->messages['term-position-updated'] ); } - return add_query_arg( $args, get_admin_url( null, 'admin.php' ) ); - } - - /** - * Handles a request to add a new piece of editorial metadata - */ - function handle_add_editorial_metadata() { - if ( !isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) - || $_GET['page'] != $this->module->settings_slug || $_POST['form-action'] != 'add-term' ) + /** + * Handles a request to delete an editorial metadata term + */ + public function handle_delete_editorial_metadata() { + if ( ! isset( $_GET['page'], $_GET['action'], $_GET['term-id'] ) + || $_GET['page'] != $this->module->settings_slug || 'delete-term' != $_GET['action'] ) { return; - - if ( !wp_verify_nonce( $_POST['_wpnonce'], 'editorial-metadata-add-nonce' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( 'manage_options' ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - // Sanitize all of the user-entered values - $term_name = sanitize_text_field( trim( $_POST['metadata_name'] ) ); - $term_slug = ( !empty( $_POST['metadata_slug'] ) ) ? sanitize_title( $_POST['metadata_slug'] ) : sanitize_title( $term_name ); - $term_description = stripslashes( wp_filter_post_kses( trim( $_POST['metadata_description'] ) ) ); - $term_type = sanitize_key( $_POST['metadata_type'] ); - - $_REQUEST['form-errors'] = array(); - + } + + if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'delete-term' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + if ( ! $existing_term = $this->get_editorial_metadata_term_by( 'id', (int) $_GET['term-id'] ) ) { + wp_die( esc_html( $this->module->messages['term-missing'] ) ); + } + + $result = $this->delete_editorial_metadata_term( $existing_term->term_id ); + if ( ! $result || is_wp_error( $result ) ) { + wp_die( esc_html__( 'Error deleting term.', 'edit-flow' ) ); + } + + $redirect_url = add_query_arg( array( + 'page' => $this->module->settings_slug, + 'message' => 'term-deleted', + ), get_admin_url( null, 'admin.php' ) ); + wp_redirect( $redirect_url ); + exit; + } + /** - * Form validation for adding new editorial metadata term + * Register settings for notifications so we can partially use the Settings API + * (We use the Settings API for form generation, but not saving) * - * Details - * - "name", "slug", and "type" are required fields - * - "description" can accept a limited amount of HTML, and is optional + * @since 0.7 + * @uses add_settings_section(), add_settings_field() */ - // Field is required - if ( empty( $term_name ) ) - $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the editorial metadata.', 'edit-flow' ); - // Field is required - if ( empty( $term_slug ) ) - $_REQUEST['form-errors']['slug'] = __( 'Please enter a slug for the editorial metadata.', 'edit-flow' ); - if ( term_exists( $term_slug ) ) - $_REQUEST['form-errors']['name'] = __( 'Name conflicts with existing term. Please choose another.', 'edit-flow' ); - // Check to ensure a term with the same name doesn't exist - if ( $this->get_editorial_metadata_term_by( 'name', $term_name, self::metadata_taxonomy ) ) - $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); - // Check to ensure a term with the same slug doesn't exist - if ( $this->get_editorial_metadata_term_by( 'slug', $term_slug ) ) - $_REQUEST['form-errors']['slug'] = __( 'Slug already in use. Please choose another.', 'edit-flow' ); - // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug - // Check that the term name doesn't exceed 200 chars - if ( strlen( $term_name ) > 200 ) - $_REQUEST['form-errors']['name'] = __( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ); - // Metadata type needs to pass our whitelist check - $metadata_types = $this->get_supported_metadata_types(); - if ( empty( $_POST['metadata_type'] ) || !isset( $metadata_types[$_POST['metadata_type'] ] ) ) - $_REQUEST['form-errors']['type'] = __( 'Please select a valid metadata type.', 'edit-flow' ); - // Metadata viewable needs to be a valid Yes or No - $term_viewable = false; - if ( $_POST['metadata_viewable'] == 'yes' ) - $term_viewable = true; - - // Kick out if there are any errors - if ( count( $_REQUEST['form-errors'] ) ) { - $_REQUEST['error'] = 'form-error'; - return; + public function register_settings() { + add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); + add_settings_field( 'post_types', __( 'Add to these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' ); } - // Try to add the status - $args = array( - 'name' => $term_name, - 'description' => $term_description, - 'slug' => $term_slug, - 'type' => $term_type, - 'viewable' => $term_viewable, - ); - $return = $this->insert_editorial_metadata_term( $args ); - if ( is_wp_error( $return ) ) - wp_die( __( 'Error adding term.', 'edit-flow' ) ); - - $redirect_url = add_query_arg( array( 'page' => $this->module->settings_slug, 'message' => 'term-added' ), get_admin_url( null, 'admin.php' ) ); - wp_redirect( $redirect_url ); - exit; - } - - /** - * Handles a request to edit an editorial metadata - */ - function handle_edit_editorial_metadata() { - if ( !isset( $_POST['submit'], $_GET['page'], $_GET['action'], $_GET['term-id'] ) - || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'edit-term' ) - return; - - if ( !wp_verify_nonce( $_POST['_wpnonce'], 'editorial-metadata-edit-nonce' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( 'manage_options' ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - if ( !$existing_term = $this->get_editorial_metadata_term_by( 'id', (int)$_GET['term-id'] ) ) - wp_die( $this->module->messages['term-missing'] ); - - $new_name = sanitize_text_field( trim( $_POST['name'] ) ); - $new_description = stripslashes( wp_filter_post_kses( strip_tags( trim( $_POST['description'] ) ) ) ); - /** - * Form validation for editing editorial metadata term + * Choose the post types for editorial metadata * - * Details - * - "name", "slug", and "type" are required fields - * - "description" can accept a limited amount of HTML, and is optional + * @since 0.7 */ - $_REQUEST['form-errors'] = array(); - // Check if name field was filled in - if( empty( $new_name ) ) - $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the editorial metadata', 'edit-flow' ); - - // Check that the name isn't numeric - if ( is_numeric( $new_name ) ) - $_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the editorial metadata.', 'edit-flow' ); - - $term_exists = term_exists( sanitize_title( $new_name ) ); - if ( $term_exists && $term_exists != $existing_term->term_id ) - $_REQUEST['form-errors']['name'] = __( 'Metadata name conflicts with existing term. Please choose another.', 'edit-flow' ); - - // Check to ensure a term with the same name doesn't exist, - $search_term = $this->get_editorial_metadata_term_by( 'name', $new_name ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) - $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); - // or that the term name doesn't map to an existing term's slug - $search_term = $this->get_editorial_metadata_term_by( 'slug', sanitize_title( $new_name ) ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) - $_REQUEST['form-errors']['name'] = __( 'Name conflicts with slug for another term. Please choose something else.', 'edit-flow' ); - - // Check that the term name doesn't exceed 200 chars - if ( strlen( $new_name ) > 200 ) - $_REQUEST['form-errors']['name'] = __( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ); - // Make sure the viewable state is valid - $new_viewable = false; - if ( $_POST['viewable'] == 'yes' ) - $new_viewable = true; - - // Kick out if there are any errors - if ( count( $_REQUEST['form-errors'] ) ) { - $_REQUEST['error'] = 'form-error'; - return; + public function settings_post_types_option() { + global $edit_flow; + $edit_flow->settings->helper_option_custom_post_type( $this->module ); } - - // Try to add the metadata term - $args = array( - 'name' => $new_name, - 'description' => $new_description, - 'viewable' => $new_viewable, - ); - $return = $this->update_editorial_metadata_term( $existing_term->term_id, $args ); - if ( is_wp_error( $return ) ) - wp_die( __( 'Error updating term.', 'edit-flow' ) ); - - $redirect_url = add_query_arg( array( 'page' => $this->module->settings_slug, 'message' => 'term-updated' ), get_admin_url( null, 'admin.php' ) ); - wp_redirect( $redirect_url ); - exit; - } - - /** - * Handle a $_GET request to change the visibility of an Editorial Metadata term - * - * @since 0.7 - */ - function handle_change_editorial_metadata_visibility() { - - // Check that the current GET request is our GET request - if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] ) - || $_GET['page'] != $this->module->settings_slug || !in_array( $_GET['action'], array( 'make-viewable', 'make-hidden' ) ) ) - return; - - // Check for proper nonce - if ( !wp_verify_nonce( $_GET['nonce'], 'make-viewable' ) && !wp_verify_nonce( $_GET['nonce'], 'make-hidden' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - // Only allow users with the proper caps - if ( !current_user_can( 'manage_options' ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - $term_id = (int)$_GET['term-id']; - $args = array(); - if ( $_GET['action'] == 'make-viewable' ) - $args['viewable'] = true; - elseif ( $_GET['action'] == 'make-hidden' ) - $args['viewable'] = false; - - $return = $this->update_editorial_metadata_term( $term_id, $args ); - if ( is_wp_error( $return ) ) - wp_die( __( 'Error updating term.', 'edit-flow' ) ); - - $redirect_url = $this->get_link( array( 'message' => 'term-visibility-changed' ) ); - wp_redirect( $redirect_url ); - exit; - - } - - /** - * Handle the request to update a given Editorial Metadata term via inline edit - * - * @since 0.7 - */ - function handle_ajax_inline_save_term() { - - if ( !wp_verify_nonce( $_POST['inline_edit'], 'editorial-metadata-inline-edit-nonce' ) ) - die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( 'manage_options') ) - die( $this->module->messages['invalid-permissions'] ); - - $term_id = (int) $_POST['term_id']; - if ( !$existing_term = $this->get_editorial_metadata_term_by( 'id', $term_id ) ) - die( $this->module->messages['term-missing'] ); - - $metadata_name = sanitize_text_field( trim( $_POST['name'] ) ); - $metadata_description = stripslashes( wp_filter_post_kses( trim( $_POST['description'] ) ) ); - + /** - * Form validation for editing editorial metadata term - */ - // Check if name field was filled in - if ( empty( $metadata_name ) ) { - $change_error = new WP_Error( 'invalid', __( 'Please enter a name for the editorial metadata', 'edit-flow' ) ); - die( $change_error->get_error_message() ); - } + * Validate data entered by the user + * + * @since 0.7 + * + * @param array $new_options New values that have been entered by the user + * @return array $new_options Form values after they've been sanitized + */ + public function settings_validate( $new_options ) { - // Check that the name isn't numeric - if( is_numeric( $metadata_name) ) { - $change_error = new WP_Error( 'invalid', __( 'Please enter a valid, non-numeric name for the editorial metadata.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); - } - - // Check that the term name doesn't exceed 200 chars - if ( strlen( $metadata_name ) > 200 ) { - $change_error = new WP_Error( 'invalid', __( 'Name cannot exceed 200 characters. Please try a shorter name.' ) ); - die( $change_error->get_error_message() ); - } - - // 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( sanitize_title( $metadata_name ) ); - if ( $term_exists && $term_exists != $term_id ) { - $change_error = new WP_Error( 'invalid', __( 'Metadata name conflicts with existing term. Please choose another.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); - } - - // Check to ensure a term with the same name doesn't exist, - $search_term = $this->get_editorial_metadata_term_by( 'name', $metadata_name ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { - $change_error = new WP_Error( 'invalid', __( 'Name already in use. Please choose another.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); - } + // Whitelist validation for the post type options + if ( ! isset( $new_options['post_types'] ) ) { + $new_options['post_types'] = array(); + } + $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); - // or that the term name doesn't map to an existing term's slug - $search_term = $this->get_editorial_metadata_term_by( 'slug', sanitize_title( $metadata_name ) ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { - $change_error = new WP_Error( 'invalid', __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); + return $new_options; } - - // Prepare the term name and description for saving - $args = array( - 'name' => $metadata_name, - 'description' => $metadata_description, - ); - $return = $this->update_editorial_metadata_term( $existing_term->term_id, $args ); - if( !is_wp_error( $return ) ) { - set_current_screen( 'edit-editorial-metadata' ); + + /** + * Prepare and display the configuration view for editorial metadata. + * There are four primary components: + * - Form to add a new Editorial Metadata term + * - Form generated by the settings API for managing Editorial Metadata options + * - Table of existing Editorial Metadata terms with ability to take actions on each + * - Full page width view for editing a single Editorial Metadata term + * + * Disabling nonce verification because that is not available here, it's just rendering it. The actual save is done in helper_settings_validate_and_save and that's guarded well. + * phpcs:disable:WordPress.Security.NonceVerification.Missing + * @since 0.7 + */ + public function print_configure_view() { + global $edit_flow; $wp_list_table = new EF_Editorial_Metadata_List_Table(); $wp_list_table->prepare_items(); - echo $wp_list_table->single_row( $return ); - die(); - } else { - $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the term: %s', 'edit-flow' ), $status_name ) ); - die( $change_error->get_error_message() ); - } - - } - - /** - * Handle the ajax request to update all of the term positions - * - * @since 0.7 - */ - function handle_ajax_update_term_positions() { - - if ( !wp_verify_nonce( $_POST['editorial_metadata_sortable_nonce'], 'editorial-metadata-sortable' ) ) - $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( 'manage_options') ) - $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] ); - - if ( !isset( $_POST['term_positions'] ) || !is_array( $_POST['term_positions'] ) ) - $this->print_ajax_response( 'error', __( 'Terms not set.', 'edit-flow' ) ); - - foreach ( $_POST['term_positions'] as $position => $term_id ) { - - // Have to add 1 to the position because the index started with zero - $args = array( - 'position' => (int)$position + 1, - ); - $return = $this->update_editorial_metadata_term( (int)$term_id, $args ); - // @todo check that this was a valid return - } - $this->print_ajax_response( 'success', $this->module->messages['term-position-updated'] ); - } - - /** - * Handles a request to delete an editorial metadata term - */ - function handle_delete_editorial_metadata() { - if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'] ) - || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'delete-term' ) - return; - - if ( !wp_verify_nonce( $_GET['nonce'], 'delete-term' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( 'manage_options' ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - if ( !$existing_term = $this->get_editorial_metadata_term_by( 'id', (int)$_GET['term-id'] ) ) - wp_die( $this->module->messages['term-missing'] ); - - $result = $this->delete_editorial_metadata_term( $existing_term->term_id ); - if ( !$result || is_wp_error( $result ) ) - wp_die( __( 'Error deleting term.', 'edit-flow' ) ); - - $redirect_url = add_query_arg( array( 'page' => $this->module->settings_slug, 'message' => 'term-deleted' ), get_admin_url( null, 'admin.php' ) ); - wp_redirect( $redirect_url ); - exit; - } - - /** - * Register settings for notifications so we can partially use the Settings API - * (We use the Settings API for form generation, but not saving) - * - * @since 0.7 - * @uses add_settings_section(), add_settings_field() - */ - function register_settings() { - add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); - add_settings_field( 'post_types', __( 'Add to these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' ); - } - - /** - * Choose the post types for editorial metadata - * - * @since 0.7 - */ - function settings_post_types_option() { - global $edit_flow; - $edit_flow->settings->helper_option_custom_post_type( $this->module ); - } - - /** - * Validate data entered by the user - * - * @since 0.7 - * - * @param array $new_options New values that have been entered by the user - * @return array $new_options Form values after they've been sanitized - */ - function settings_validate( $new_options ) { - - // Whitelist validation for the post type options - if ( !isset( $new_options['post_types'] ) ) - $new_options['post_types'] = array(); - $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); - - return $new_options; - } - - /** - * Prepare and display the configuration view for editorial metadata. - * There are four primary components: - * - Form to add a new Editorial Metadata term - * - Form generated by the settings API for managing Editorial Metadata options - * - Table of existing Editorial Metadata terms with ability to take actions on each - * - Full page width view for editing a single Editorial Metadata term - * - * @since 0.7 - */ - function print_configure_view() { - global $edit_flow; - $wp_list_table = new EF_Editorial_Metadata_List_Table(); - $wp_list_table->prepare_items(); - ?> + ?> - +
- display(); ?> - + display(); ?> +
- inline_edit(); ?> - - - - - get_editorial_metadata_term_by( 'id', $term_id ); - if ( !$term ) { - echo '

' . $this->module->messages['term-missing'] . '

'; - return; - } - $metadata_types = $this->get_supported_metadata_types(); - $type = $term->type; - $edit_term_link = $this->get_link( array( 'action' => 'edit-term', 'term-id' => $term->term_id ) ); - - $name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $term->name; - $description = ( isset( $_POST['description'] ) ) ? stripslashes( $_POST['description'] ) : $term->description; - if ( $term->viewable ) - $viewable = 'yes'; - else - $viewable = 'no'; - $viewable = ( isset( $_POST['viewable'] ) ) ? stripslashes( $_POST['viewable'] ) : $viewable; - ?> - + inline_edit(); ?> + + + + + get_editorial_metadata_term_by( 'id', $term_id ); + if ( ! $term ) { + echo '

' . esc_html( $this->module->messages['term-missing'] ) . '

'; + return; + } + $metadata_types = $this->get_supported_metadata_types(); + $type = $term->type; + $edit_term_link = $this->get_link( array( + 'action' => 'edit-term', + 'term-id' => $term->term_id, + ) ); + + $name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $term->name; + $description = ( isset( $_POST['description'] ) ) ? stripslashes( $_POST['description'] ) : $term->description; + if ( $term->viewable ) { + $viewable = 'yes'; + } else { + $viewable = 'no'; + } + $viewable = ( isset( $_POST['viewable'] ) ) ? stripslashes( $_POST['viewable'] ) : $viewable; + ?> +
- - + + @@ -1447,7 +1537,7 @@ function print_configure_view() {

- + @@ -1470,57 +1560,81 @@ function print_configure_view() { 'no' => __( 'No', 'edit-flow' ), 'yes' => __( 'Yes', 'edit-flow' ), ); - ?> + ?> settings->helper_print_error_or_description( 'viewable', __( 'When viewable, metadata can be seen on views other than the edit post view (e.g. calendar, manage posts, story budget, etc.)', 'edit-flow' ) ); ?> - +
@@ -1458,7 +1548,7 @@ function print_configure_view() {
- +

- +

- - - + + +
-
+
- - - -
- module->options_group_name ); ?> - module->options_group_name ); ?> - module->name ) . '" />'; ?> - + + + + + module->options_group_name ); ?> + module->options_group_name ); ?> + module->name ) . '" />'; ?> +
- - + +
- + settings->helper_print_error_or_description( 'name', __( 'The name is for labeling the metadata field.', 'edit-flow' ) ); ?>
- + settings->helper_print_error_or_description( 'slug', __( 'The "slug" is the URL-friendly version of the name. It is usually all lowercase and contains only letters, numbers, and hyphens.', 'edit-flow' ) ); ?>
- + settings->helper_print_error_or_description( 'description', __( 'The description can be used to communicate with your team about what the metadata is for.', 'edit-flow' ) ); ?>
@@ -1532,7 +1646,7 @@ function print_configure_view() { ?> settings->helper_print_error_or_description( 'type', __( 'Indicate the type of editorial metadata.', 'edit-flow' ) ); ?> @@ -1545,57 +1659,60 @@ function print_configure_view() { 'yes' => __( 'Yes', 'edit-flow' ), ); $current_metadata_viewable = ( isset( $_POST['metadata_viewable'] ) && in_array( $_POST['metadata_viewable'], array_keys( $metadata_viewable_options ) ) ) ? $_POST['metadata_viewable'] : 'no'; - ?> + ?> settings->helper_print_error_or_description( 'viewable', __( 'When viewable, metadata can be seen on views other than the edit post view (e.g. calendar, manage posts, story budget, etc.)', 'edit-flow' ) ); ?>
- + -

+

- - taxonomy = EF_Editorial_Metadata::metadata_taxonomy; - + $this->tax = get_taxonomy( $this->taxonomy ); - + $columns = $this->get_columns(); $hidden = array( 'position', ); $sortable = array(); - - $this->_column_headers = array( $columns, $hidden, $sortable ); + + $this->_column_headers = array( $columns, $hidden, $sortable ); parent::__construct( array( 'plural' => 'editorial metadata', @@ -1608,7 +1725,7 @@ function __construct() { * * @since 0.7 */ - function prepare_items() { + public function prepare_items() { global $edit_flow; $this->items = $edit_flow->editorial_metadata->get_editorial_metadata_terms(); @@ -1623,27 +1740,27 @@ function prepare_items() { * * @since 0.7 */ - function no_items() { + public function no_items() { _e( 'No editorial metadata found.', 'edit-flow' ); } - + /** * Register the columns to appear in the table * * @since 0.7 */ - function get_columns() { - + public function get_columns() { + $columns = array( - 'position' => __( 'Position', 'edit-flow' ), + 'position' => __( 'Position', 'edit-flow' ), 'name' => __( 'Name', 'edit-flow' ), - 'type' => __( 'Metadata Type', 'edit-flow' ), + 'type' => __( 'Metadata Type', 'edit-flow' ), 'description' => __( 'Description', 'edit-flow' ), 'viewable' => __( 'Viewable', 'edit-flow' ), - ); + ); return $columns; } - + /** * Prepare a single row of Editorial Metadata * @@ -1652,16 +1769,16 @@ function get_columns() { * @param object $term The current term we're displaying * @param int $level Level is always zero because it isn't a parent-child tax */ - function single_row( $term, $level = 0 ) { + public function single_row( $term, $level = 0 ) { static $alternate_class = ''; - $alternate_class = ( $alternate_class == '' ? ' alternate' : '' ); + $alternate_class = ( '' == $alternate_class ? ' alternate' : '' ); $row_class = ' class="term-static' . $alternate_class . '"'; - echo ''; - echo $this->single_row_columns( $term ); - echo ''; + echo wp_kses_post( '' ); + echo wp_kses_post( $this->single_row_columns( $term ) ); + echo ''; } - + /** * Handle the column output when there's no method for it * @@ -1670,24 +1787,24 @@ function single_row( $term, $level = 0 ) { * @param object $item Editorial Metadata term as an object * @param string $column_name How the column was registered at birth */ - function column_default( $item, $column_name ) { - - switch( $column_name ) { + public function column_default( $item, $column_name ) { + + switch ( $column_name ) { case 'position': case 'type': case 'description': return esc_html( $item->$column_name ); break; case 'viewable': - if ( $item->viewable ) + if ( $item->viewable ) { return __( 'Yes', 'edit-flow' ); - else + } else { return __( 'No', 'edit-flow' ); + } break; default: break; } - } /** @@ -1697,28 +1814,41 @@ function column_default( $item, $column_name ) { * * @param object $item Editorial Metadata term as an object */ - function column_name( $item ) { + public function column_name( $item ) { global $edit_flow; - $item_edit_link = esc_url( $edit_flow->editorial_metadata->get_link( array( 'action' => 'edit-term', 'term-id' => $item->term_id ) ) ); - $item_delete_link = esc_url( $edit_flow->editorial_metadata->get_link( array( 'action' => 'delete-term', 'term-id' => $item->term_id ) ) ); - + $item_edit_link = esc_url( $edit_flow->editorial_metadata->get_link( array( + 'action' => 'edit-term', + 'term-id' => $item->term_id, + ) ) ); + $item_delete_link = esc_url( $edit_flow->editorial_metadata->get_link( array( + 'action' => 'delete-term', + 'term-id' => $item->term_id, + ) ) ); + $out = '' . esc_html( $item->name ) . ''; - + $actions = array(); - $actions['edit'] = "" . __( 'Edit', 'edit-flow' ) . ""; + $actions['edit'] = "" . __( 'Edit', 'edit-flow' ) . ''; $actions['inline hide-if-no-js'] = '' . __( 'Quick Edit' ) . ''; - if ( $item->viewable ) - $actions['change-visibility make-hidden'] = '' . __( 'Make Hidden', 'edit-flow' ) . ''; - else - $actions['change-visibility make-viewable'] = '' . __( 'Make Viewable', 'edit-flow' ) . ''; - $actions['delete delete-status'] = "" . __( 'Delete', 'edit-flow' ) . ""; - + if ( $item->viewable ) { + $actions['change-visibility make-hidden'] = '' . __( 'Make Hidden', 'edit-flow' ) . ''; + } else { + $actions['change-visibility make-viewable'] = '' . __( 'Make Viewable', 'edit-flow' ) . ''; + } + $actions['delete delete-status'] = "" . __( 'Delete', 'edit-flow' ) . ''; + $out .= $this->row_actions( $actions, false ); $out .= ''; - + return $out; } @@ -1727,11 +1857,11 @@ function column_name( $item ) { * * @since 0.7 */ - function inline_edit() { + public function inline_edit() { -?> + ?>
-
- Date: Wed, 26 Jun 2024 16:07:34 +1000 Subject: [PATCH 09/15] lint the story budget module --- modules/story-budget/story-budget.php | 330 ++++++++++++++------------ 1 file changed, 179 insertions(+), 151 deletions(-) diff --git a/modules/story-budget/story-budget.php b/modules/story-budget/story-budget.php index ee524097..43285cba 100644 --- a/modules/story-budget/story-budget.php +++ b/modules/story-budget/story-budget.php @@ -7,24 +7,27 @@ */ class EF_Story_Budget extends EF_Module { - var $taxonomy_used = 'category'; + public $taxonomy_used = 'category'; - var $module; + public $module; - var $num_columns = 0; + public $num_columns = 0; - var $max_num_columns; + public $max_num_columns; - var $no_matching_posts = true; + public $no_matching_posts = true; - var $terms = array(); + public $terms = array(); - var $user_filters; + public $user_filters; + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase const screen_id = 'dashboard_page_story-budget'; + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase const usermeta_key_prefix = 'ef_story_budget_'; + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase const default_num_columns = 1; private $term_columns; @@ -32,12 +35,13 @@ class EF_Story_Budget extends EF_Module { /** * Register the module with Edit Flow but don't do anything else */ - function __construct() { + public function __construct() { $this->module_url = $this->get_module_url( __FILE__ ); // Register the module with Edit Flow $args = array( 'title' => __( 'Story Budget', 'edit-flow' ), + // translators: %s is a link to the story budget page 'short_description' => sprintf( __( 'View the status of all your content at a glance.', 'edit-flow' ), admin_url( 'index.php?page=story-budget' ) ), 'extended_description' => __( 'Use the story budget to see how content on your site is progressing. Filter by specific categories or date ranges to see details about each post in progress.', 'edit-flow' ), 'module_url' => $this->module_url, @@ -50,17 +54,17 @@ function __construct() { 'autoload' => false, ); $this->module = EditFlow()->register_module( 'story_budget', $args ); - } /** * Initialize the rest of the stuff in the class if the module is active */ - function init() { + public function init() { $view_story_budget_cap = apply_filters( 'ef_view_story_budget_cap', 'ef_view_story_budget' ); - if ( !current_user_can( $view_story_budget_cap ) ) + if ( ! current_user_can( $view_story_budget_cap ) ) { return; + } $this->num_columns = $this->get_num_columns(); $this->max_num_columns = apply_filters( 'ef_story_budget_max_num_columns', 3 ); @@ -78,7 +82,6 @@ function init() { // Load necessary scripts and stylesheets add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'action_enqueue_admin_styles' ) ); - } /** @@ -86,15 +89,15 @@ function init() { * * @since 0.7 */ - function install() { + public function install() { $story_budget_roles = array( 'administrator' => array( 'ef_view_story_budget' ), - 'editor' => array( 'ef_view_story_budget' ), - 'author' => array( 'ef_view_story_budget' ), - 'contributor' => array( 'ef_view_story_budget' ) + 'editor' => array( 'ef_view_story_budget' ), + 'author' => array( 'ef_view_story_budget' ), + 'contributor' => array( 'ef_view_story_budget' ), ); - foreach( $story_budget_roles as $role => $caps ) { + foreach ( $story_budget_roles as $role => $caps ) { $this->add_caps_to_role( $role, $caps ); } } @@ -104,23 +107,23 @@ function install() { * * @since 0.7 */ - function upgrade( $previous_version ) { + public function upgrade( $previous_version ) { global $edit_flow; // Upgrade path to v0.7 - if ( version_compare( $previous_version, '0.7' , '<' ) ) { + if ( version_compare( $previous_version, '0.7', '<' ) ) { // Migrate whether the story budget was enabled or not and clean up old option - if ( $enabled = get_option( 'edit_flow_story_budget_enabled' ) ) + if ( $enabled = get_option( 'edit_flow_story_budget_enabled' ) ) { $enabled = 'on'; - else + } else { $enabled = 'off'; + } $edit_flow->update_module_option( $this->module->name, 'enabled', $enabled ); delete_option( 'edit_flow_story_budget_enabled' ); // Technically we've run this code before so we don't want to auto-install new data $edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); } - } /** @@ -128,8 +131,8 @@ function upgrade( $previous_version ) { * * @uses add_submenu_page() */ - function action_admin_menu() { - add_submenu_page( 'index.php', __('Story Budget', 'edit-flow'), __('Story Budget', 'edit-flow'), apply_filters( 'ef_view_story_budget_cap', 'ef_view_story_budget' ), $this->module->slug, array( $this, 'story_budget') ); + public function action_admin_menu() { + add_submenu_page( 'index.php', __( 'Story Budget', 'edit-flow' ), __( 'Story Budget', 'edit-flow' ), apply_filters( 'ef_view_story_budget_cap', 'ef_view_story_budget' ), $this->module->slug, array( $this, 'story_budget' ) ); } /** @@ -137,11 +140,12 @@ function action_admin_menu() { * * @uses enqueue_admin_script() */ - function enqueue_admin_scripts() { + public function enqueue_admin_scripts() { global $current_screen; - if ( $current_screen->id != self::screen_id ) + if ( self::screen_id != $current_screen->id ) { return; + } $num_columns = $this->get_num_columns(); echo ''; @@ -153,11 +157,12 @@ function enqueue_admin_scripts() { /** * Enqueue a screen and print stylesheet for the story budget. */ - function action_enqueue_admin_styles() { + public function action_enqueue_admin_styles() { global $current_screen; - if ( $current_screen->id != self::screen_id ) + if ( self::screen_id != $current_screen->id ) { return; + } wp_enqueue_style( 'edit_flow-story_budget-styles', $this->module_url . 'lib/story-budget.css', false, EDIT_FLOW_VERSION, 'screen' ); wp_enqueue_style( 'edit_flow-story_budget-print-styles', $this->module_url . 'lib/story-budget-print.css', false, EDIT_FLOW_VERSION, 'print' ); @@ -169,7 +174,7 @@ function action_enqueue_admin_styles() { * * @since 0.7 */ - function register_term_columns() { + public function register_term_columns() { $term_columns = array( 'title' => __( 'Title', 'edit-flow' ), @@ -188,17 +193,18 @@ function register_term_columns() { * * @since 0.7 */ - function handle_form_date_range_change() { + public function handle_form_date_range_change() { if ( ! isset( $_POST['ef-story-budget-range-submit'], $_POST['ef-story-budget-number-days'], $_POST['ef-story-budget-start-date_hidden'] ) ) { return; } - if ( !wp_verify_nonce( $_POST['nonce'], 'change-date' ) ) - wp_die( $this->module->messages['nonce-failed'] ); + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'change-date' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } $current_user = wp_get_current_user(); - $new_filters = array ( + $new_filters = array( 'start_date' => $_POST['ef-story-budget-start-date_hidden'], 'number_days' => (int) $_POST['ef-story-budget-number-days'], ); @@ -245,7 +251,7 @@ public function update_user_filters_from_form_date_range_change( $current_user, /** * Get the number of columns to show on the story budget */ - function get_num_columns() { + public function get_num_columns() { if ( empty( $this->num_columns ) ) { $current_user = wp_get_current_user(); @@ -264,18 +270,18 @@ function get_num_columns() { * * @since 0.8.3 */ - function add_screen_options_panel() { - require_once( EDIT_FLOW_ROOT . '/common/php/' . 'screen-options.php' ); + public function add_screen_options_panel() { + require_once EDIT_FLOW_ROOT . '/common/php/screen-options.php'; add_screen_options_panel( self::usermeta_key_prefix . 'screen_columns', __( 'Screen Layout', 'edit-flow' ), array( $this, 'print_column_prefs' ), self::screen_id, array( $this, 'save_column_prefs' ), true ); } /** * Print column number preferences for screen options */ - function print_column_prefs() { + public function print_column_prefs() { $return_val = __( 'Number of Columns: ', 'edit-flow' ); for ( $i = 1; $i <= $this->max_num_columns; ++$i ) { - $return_val .= "\n"; + $return_val .= "\n"; } return $return_val; } @@ -283,7 +289,7 @@ function print_column_prefs() { /** * Save the current user's preference for number of columns. */ - function save_column_prefs( $posted_fields ) { + public function save_column_prefs( $posted_fields ) { $key = self::usermeta_key_prefix . 'screen_columns'; $this->num_columns = (int) $posted_fields[ $key ]; @@ -297,23 +303,23 @@ function save_column_prefs( $posted_fields ) { * ouput any messages, create the table navigation, then print the columns based on * get_num_columns(), which will in turn print the stories themselves. */ - function story_budget() { + public function story_budget() { // Update the current user's filters with the variables set in $_GET $this->user_filters = $this->update_user_filters(); - if ( !empty( $this->user_filters[$this->taxonomy_used] ) ) { + if ( ! empty( $this->user_filters[ $this->taxonomy_used ] ) ) { $terms = array(); - $terms[] = get_term( $this->user_filters[$this->taxonomy_used], $this->taxonomy_used ); + $terms[] = get_term( $this->user_filters[ $this->taxonomy_used ], $this->taxonomy_used ); } else { // Get all of the terms from the taxonomy, regardless whether there are published posts - $args = array( + $terms = get_terms( array( + 'taxonomy' => $this->taxonomy_used, 'orderby' => 'name', 'order' => 'asc', 'hide_empty' => 0, 'parent' => 0, - ); - $terms = get_terms( $this->taxonomy_used, $args ); + )); } $this->terms = apply_filters( 'ef_story_budget_filter_terms', $terms ); // allow for reordering or any other filtering of terms @@ -328,22 +334,22 @@ function story_budget() {
num_columns ) . '">'; - foreach( (array) $this->terms as $term ) { - $this->print_term( $term ); - } + foreach ( (array) $this->terms as $term ) { + $this->print_term( $term ); + } echo '
'; ?>
@@ -355,9 +361,9 @@ function story_budget() { * * @since 0.7 */ - function story_budget_time_range() { + public function story_budget_time_range() { ?> -
+ user_filters['start_date'] ) ) ); ?> @@ -382,7 +388,7 @@ function story_budget_time_range() { * @param object $term The term we're getting posts for * @return array $term_posts An array of post objects for the term */ - function get_posts_for_term( $term, $args = null ) { + public function get_posts_for_term( $term, $args = null ) { $defaults = array( 'post_status' => null, @@ -395,7 +401,7 @@ function get_posts_for_term( $term, $args = null ) { $arg_terms = array( $term->term_id, ); - $arg_terms = array_merge( $arg_terms, get_term_children( $term->term_id, $this->taxonomy_used ) ) ; + $arg_terms = array_merge( $arg_terms, get_term_children( $term->term_id, $this->taxonomy_used ) ); $args['tax_query'] = array( array( 'taxonomy' => $this->taxonomy_used, @@ -418,15 +424,17 @@ function get_posts_for_term( $term, $args = null ) { } // Filter by post_author if it's set - if ( $args['author'] === '0' ) unset( $args['author'] ); + if ( '0' === $args['author'] ) { + unset( $args['author'] ); + } $beginning_date = strtotime( $this->user_filters['start_date'] ); $days_to_show = $this->user_filters['number_days']; $ending_date = $beginning_date + ( $days_to_show * DAY_IN_SECONDS ); $args['date_query'] = array( - 'after' => date( "Y-m-d", $beginning_date ), - 'before' => date( "Y-m-d", $ending_date ), + 'after' => gmdate( 'Y-m-d', $beginning_date ), + 'before' => gmdate( 'Y-m-d', $ending_date ), 'inclusive' => true, ); @@ -451,23 +459,29 @@ function get_posts_for_term( $term, $args = null ) { * * @param object $term The term to print. */ - function print_term( $term ) { + public function print_term( $term ) { global $wpdb; $posts = $this->get_posts_for_term( $term, $this->user_filters ); - if ( !empty( $posts ) ) + if ( ! empty( $posts ) ) { // Don't display the message for $no_matching_posts $this->no_matching_posts = false; + } - ?> -
+ ?> +

name ); ?>

- + - term_columns as $key => $name ): ?> + term_columns as $key => $name ) : ?> @@ -475,17 +489,18 @@ function print_term( $term ) { print_post( $post, $term ); + foreach ( $posts as $post ) { + $this->print_post( $post, $term ); + } ?>
- +

- - term_columns as $key => $name ) { + term_columns as $key => $name ) { echo ''; if ( method_exists( $this, 'term_column_' . $key ) ) { $method = 'term_column_' . $key; - echo $this->$method( $post, $parent_term ); + echo wp_kses_post( $this->$method( $post, $parent_term ) ); } else { - echo $this->term_column_default( $post, $key, $parent_term ); + echo wp_kses_post( $this->term_column_default( $post, $key, $parent_term ) ); } echo ''; - } ?> + } + ?> post_status ); return $status_name->label; @@ -545,12 +563,12 @@ function term_column_default( $post, $column_name, $parent_term ) { return $output; break; case 'post_modified': + // translators: %s is a human-readable time difference return sprintf( esc_html__( '%s ago', 'edit-flow' ), human_time_diff( get_the_time( 'U', $post->ID ), current_time( 'timestamp' ) ) ); break; default: break; } - } /** @@ -558,29 +576,35 @@ function term_column_default( $post, $column_name, $parent_term ) { * * @since 0.7 */ - function term_column_title( $post, $parent_term ) { + public function term_column_title( $post, $parent_term ) { $post_title = _draft_or_post_title( $post->ID ); $post_type_object = get_post_type_object( $post->post_type ); $can_edit_post = current_user_can( $post_type_object->cap->edit_post, $post->ID ); - if ( $can_edit_post ) + if ( $can_edit_post ) { $output = '' . esc_html( $post_title ) . ''; - else + } else { $output = '' . esc_html( $post_title ) . ''; + } // Edit or Trash or View $output .= '
'; $item_actions = array(); - if ( $can_edit_post ) + if ( $can_edit_post ) { $item_actions['edit'] = '' . __( 'Edit', 'edit-flow' ) . ''; - if ( EMPTY_TRASH_DAYS > 0 && current_user_can( $post_type_object->cap->delete_post, $post->ID ) ) + } + if ( EMPTY_TRASH_DAYS > 0 && current_user_can( $post_type_object->cap->delete_post, $post->ID ) ) { $item_actions['trash'] = '' . __( 'Trash', 'edit-flow' ) . ''; + } // Display a View or a Preview link depending on whether the post has been published or not - if ( in_array( $post->post_status, array( 'publish' ) ) ) + if ( in_array( $post->post_status, array( 'publish' ) ) ) { + // translators: %s is the post title $item_actions['view'] = '' . __( 'View', 'edit-flow' ) . ''; - else if ( $can_edit_post ) + } else if ( $can_edit_post ) { + // translators: %s is the post title $item_actions['previewpost'] = '' . __( 'Preview', 'edit-flow' ) . ''; + } $item_actions = apply_filters( 'ef_story_budget_item_actions', $item_actions, $post->ID ); if ( count( $item_actions ) ) { @@ -599,26 +623,28 @@ function term_column_title( $post, $parent_term ) { /** * Print any messages that should appear based on the action performed */ - function print_messages() { - ?> + public function print_messages() { + ?> -

'; // Following mostly stolen from edit.php if ( isset( $_GET['trashed'] ) && (int) $_GET['trashed'] ) { - printf( _n( 'Item moved to the trash.', '%d items moved to the trash.', $_GET['trashed'] ), number_format_i18n( $_GET['trashed'] ) ); - $ids = isset($_GET['ids']) ? $_GET['ids'] : 0; - echo ' ' . __( 'Undo', 'edit-flow' ) . '
'; - unset($_GET['trashed']); + // translators: %d is the number of posts trashed + printf( esc_html( _n( 'Item moved to the trash.', '%d items moved to the trash.', $_GET['trashed'] ), number_format_i18n( $_GET['trashed'] ) ) ); + $ids = isset( $_GET['ids'] ) ? $_GET['ids'] : 0; + echo ' ' . esc_html__( 'Undo', 'edit-flow' ) . '
'; + unset( $_GET['trashed'] ); } - if ( isset($_GET['untrashed'] ) && (int) $_GET['untrashed'] ) { - printf( _n( 'Item restored from the Trash.', '%d items restored from the Trash.', $_GET['untrashed'] ), number_format_i18n( $_GET['untrashed'] ) ); - unset($_GET['undeleted']); + if ( isset( $_GET['untrashed'] ) && (int) $_GET['untrashed'] ) { + // translators: %d is the number of posts restored from the trash + printf( esc_html( _n( 'Item restored from the Trash.', '%d items restored from the Trash.', $_GET['untrashed'] ), number_format_i18n( $_GET['untrashed'] ) ) ); + unset( $_GET['undeleted'] ); } echo '

'; @@ -628,16 +654,16 @@ function print_messages() { /** * Print the table navigation and filter controls, using the current user's filters if any are set. */ - function table_navigation() { - ?> + public function table_navigation() { + ?>
story_budget_filters() as $select_id => $select_name ) { - echo $this->story_budget_filter_options( $select_id, $select_name, $this->user_filters ); - } + foreach ( $this->story_budget_filters() as $select_id => $select_name ) { + echo wp_kses_post( $this->story_budget_filter_options( $select_id, $select_name, $this->user_filters ) ); + } ?> @@ -647,8 +673,8 @@ function table_navigation() { story_budget_filters() as $select_id => $select_name ) { - echo ''; + foreach ( $this->story_budget_filters() as $select_id => $select_name ) { + echo ''; } ?> @@ -661,23 +687,23 @@ function table_navigation() {
- $this->filter_get_param( 'post_status' ), - 'cat' => $this->filter_get_param( 'cat' ), - 'author' => $this->filter_get_param( 'author' ), - 'start_date' => $this->filter_get_param( 'start_date' ), - 'number_days' => $this->filter_get_param( 'number_days' ) + 'post_status' => $this->filter_get_param( 'post_status' ), + 'cat' => $this->filter_get_param( 'cat' ), + 'author' => $this->filter_get_param( 'author' ), + 'start_date' => $this->filter_get_param( 'start_date' ), + 'number_days' => $this->filter_get_param( 'number_days' ), ); $current_user_filters = array(); @@ -685,18 +711,20 @@ function update_user_filters() { // If any of the $_GET vars are missing, then use the current user filter foreach ( $user_filters as $key => $value ) { - if ( is_null( $value ) && !empty( $current_user_filters[$key] ) ) { - $user_filters[$key] = $current_user_filters[$key]; + if ( is_null( $value ) && ! empty( $current_user_filters[ $key ] ) ) { + $user_filters[ $key ] = $current_user_filters[ $key ]; } } - if ( !$user_filters['start_date'] ) - $user_filters['start_date'] = date( 'Y-m-d' ); + if ( ! $user_filters['start_date'] ) { + $user_filters['start_date'] = gmdate( 'Y-m-d' ); + } - if ( !$user_filters['number_days'] ) + if ( ! $user_filters['number_days'] ) { $user_filters['number_days'] = 10; + } - $user_filters = apply_filters('ef_story_budget_filter_values', $user_filters, $current_user_filters); + $user_filters = apply_filters( 'ef_story_budget_filter_values', $user_filters, $current_user_filters ); $this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $user_filters ); return $user_filters; @@ -708,15 +736,16 @@ function update_user_filters() { * * @return array The filters for the current user, or the default filters if the current user has none. */ - function get_user_filters() { + public function get_user_filters() { $current_user = wp_get_current_user(); $user_filters = array(); $user_filters = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true ); // If usermeta didn't have filters already, insert defaults into DB - if ( empty( $user_filters ) ) + if ( empty( $user_filters ) ) { $user_filters = $this->update_user_filters(); + } return $user_filters; } @@ -724,72 +753,72 @@ function get_user_filters() { * * @param string $param The parameter to look for in $_GET * @return null if the parameter is not set in $_GET, empty string if the parameter is empty in $_GET, - * or a sanitized version of the parameter from $_GET if set and not empty + * or a sanitized version of the parameter from $_GET if set and not empty */ - function filter_get_param( $param ) { + public function filter_get_param( $param ) { // Sure, this could be done in one line. But we're cooler than that: let's make it more readable! - if ( !isset( $_GET[$param] ) ) { + if ( ! isset( $_GET[ $param ] ) ) { return null; - } else if ( empty( $_GET[$param] ) ) { + } else if ( empty( $_GET[ $param ] ) ) { return ''; } - return sanitize_key( $_GET[$param] ); + return sanitize_key( $_GET[ $param ] ); } - function story_budget_filters() { + public function story_budget_filters() { $select_filter_names = array(); $select_filter_names['post_status'] = 'post_status'; $select_filter_names['cat'] = 'cat'; $select_filter_names['author'] = 'author'; - return apply_filters('ef_story_budget_filter_names', $select_filter_names); + return apply_filters( 'ef_story_budget_filter_names', $select_filter_names ); } - function story_budget_filter_options( $select_id, $select_name, $filters ) { - switch( $select_id ) { + public function story_budget_filter_options( $select_id, $select_name, $filters ) { + switch ( $select_id ) { case 'post_status': - $post_stati = $this->get_budget_post_stati(); - ?> + $post_stati = $this->get_budget_post_stati(); + ?> - __( 'View all categories', 'edit-flow' ), 'hide_empty' => 0, 'hierarchical' => 1, 'show_count' => 0, 'orderby' => 'name', - 'selected' => $this->user_filters['cat'] - ); + 'selected' => $this->user_filters['cat'], + ); wp_dropdown_categories( $category_dropdown_args ); } - break; + break; case 'author': $users_dropdown_args = array( - 'show_option_all' => __( 'View all users', 'edit-flow' ), - 'name' => 'author', - 'selected' => $this->user_filters['author'], - 'who' => 'authors', - ); + 'show_option_all' => __( 'View all users', 'edit-flow' ), + 'name' => 'author', + 'selected' => $this->user_filters['author'], + 'who' => 'authors', + ); $users_dropdown_args = apply_filters( 'ef_story_budget_users_dropdown_args', $users_dropdown_args ); wp_dropdown_users( $users_dropdown_args ); - break; + break; default: - do_action( 'ef_story_budget_filter_display', $select_id, $select_name, $filters); - break; + do_action( 'ef_story_budget_filter_display', $select_id, $select_name, $filters ); + break; } } @@ -808,13 +837,12 @@ public function get_budget_post_stati() { $final_statuses = []; - foreach( $post_stati as $status ) { - if ( !empty( $custom_status_slug_keys[ $status->name ] ) ) { + foreach ( $post_stati as $status ) { + if ( ! empty( $custom_status_slug_keys[ $status->name ] ) ) { $final_statuses[] = $status; } } return apply_filters( 'ef_budget_post_stati', $final_statuses ); } - } From 1a2a6a11f5b4aa322f1d64149fcc21aec532549a Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 26 Jun 2024 16:44:10 +1000 Subject: [PATCH 10/15] Lint the user groups module as well --- modules/user-groups/user-groups.php | 2243 ++++++++++++++------------- 1 file changed, 1163 insertions(+), 1080 deletions(-) diff --git a/modules/user-groups/user-groups.php b/modules/user-groups/user-groups.php index 1c81ddda..7508b875 100644 --- a/modules/user-groups/user-groups.php +++ b/modules/user-groups/user-groups.php @@ -1,7 +1,7 @@ module_url = $this->get_module_url( __FILE__ ); - - // Register the User Groups module with Edit Flow - $args = array( - 'title' => __( 'User Groups', 'edit-flow' ), - 'short_description' => __( 'Organize your users into groups to mimic your organizational structure.', 'edit-flow' ), - 'extended_description' => __( 'Configure user groups to organize all of the users on your site. Each user can be in many user groups and you can change them at any time.', 'edit-flow' ), - 'module_url' => $this->module_url, - 'img_url' => $this->module_url . 'lib/usergroups_s128.png', - 'slug' => 'user-groups', - 'default_options' => array( - 'enabled' => 'on', - 'post_types' => array( - 'post' => 'on', - 'page' => 'off', + /** + * Keys for storing data + * - taxonomy_key - used for custom taxonomy + * - term_prefix - Used for custom taxonomy terms + */ + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase + const taxonomy_key = 'ef_usergroup'; + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase + const term_prefix = 'ef-usergroup-'; + + public $manage_usergroups_cap = 'edit_usergroups'; + + /** + * Register the module with Edit Flow but don't do anything else + * + * @since 0.7 + */ + public function __construct() { + + $this->module_url = $this->get_module_url( __FILE__ ); + + // Register the User Groups module with Edit Flow + $args = array( + 'title' => __( 'User Groups', 'edit-flow' ), + 'short_description' => __( 'Organize your users into groups to mimic your organizational structure.', 'edit-flow' ), + 'extended_description' => __( 'Configure user groups to organize all of the users on your site. Each user can be in many user groups and you can change them at any time.', 'edit-flow' ), + 'module_url' => $this->module_url, + 'img_url' => $this->module_url . 'lib/usergroups_s128.png', + 'slug' => 'user-groups', + 'default_options' => array( + 'enabled' => 'on', + 'post_types' => array( + 'post' => 'on', + 'page' => 'off', + ), ), - ), - 'messages' => array( - 'usergroup-added' => __( "User group created. Feel free to add users to the usergroup.", 'edit-flow' ), - 'usergroup-updated' => __( "User group updated.", 'edit-flow' ), - 'usergroup-missing' => __( "User group doesn't exist.", 'edit-flow' ), - 'usergroup-deleted' => __( "User group deleted.", 'edit-flow' ), - ), - 'configure_page_cb' => 'print_configure_view', - 'configure_link_text' => __( 'Manage User Groups', 'edit-flow' ), - 'autoload' => false, - 'settings_help_tab' => array( - 'id' => 'ef-user-groups-overview', - 'title' => __('Overview', 'edit-flow'), - 'content' => __('

For those with many people involved in the publishing process, user groups helps you keep them organized.

Currently, user groups are primarily used for subscribing a set of users to a post for notifications.

', 'edit-flow'), + 'messages' => array( + 'usergroup-added' => __( 'User group created. Feel free to add users to the usergroup.', 'edit-flow' ), + 'usergroup-updated' => __( 'User group updated.', 'edit-flow' ), + 'usergroup-missing' => __( "User group doesn't exist.", 'edit-flow' ), + 'usergroup-deleted' => __( 'User group deleted.', 'edit-flow' ), ), - 'settings_help_sidebar' => __( '

For more information:

User Groups Documentation

Edit Flow Forum

Edit Flow on Github

', 'edit-flow' ), - ); - $this->module = EditFlow()->register_module( 'user_groups', $args ); - - } - - /** - * Module startup - */ - - /** - * Initialize the rest of the stuff in the class if the module is active - * - * @since 0.7 - */ - function init() { - - // Register the objects where we'll be storing data and relationships - $this->register_usergroup_objects(); - - $this->manage_usergroups_cap = apply_filters( 'ef_manage_usergroups_cap', $this->manage_usergroups_cap ); - - // Register our settings - add_action( 'admin_init', array( $this, 'register_settings' ) ); - - // Handle any adding, editing or saving - add_action( 'admin_init', array( $this, 'handle_add_usergroup' ) ); - add_action( 'admin_init', array( $this, 'handle_edit_usergroup' ) ); - add_action( 'admin_init', array( $this, 'handle_delete_usergroup' ) ); - add_action( 'wp_ajax_inline_save_usergroup', array( $this, 'handle_ajax_inline_save_usergroup' ) ); - - // Usergroups can be managed from the User profile view - add_action( 'show_user_profile', array( $this, 'user_profile_page' ) ); - add_action( 'edit_user_profile', array( $this, 'user_profile_page' ) ); - add_action( 'user_profile_update_errors', array( $this, 'user_profile_update' ), 10, 3 ); - - // Javascript and CSS if we need it - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); - - } - - /** - * Load the capabilities onto users the first time the module is run - * - * @since 0.7 - */ - function install() { - - // Add necessary capabilities to allow management of user groups - $usergroup_roles = array( - 'administrator' => array('edit_usergroups'), - ); - foreach( $usergroup_roles as $role => $caps ) { - $this->add_caps_to_role( $role, $caps ); - } - - // Create our default usergroups - $default_usergroups = array( - array( - 'name' => __( 'Copy Editors', 'edit-flow' ), - 'description' => __( 'Making sure the quality is top-notch.', 'edit-flow' ), - ), - array( - 'name' => __( 'Photographers', 'edit-flow' ), - 'description' => __( 'Capturing the story visually.', 'edit-flow' ), - ), - array( - 'name' => __( 'Reporters', 'edit-flow' ), - 'description' => __( 'Out in the field, writing stories.', 'edit-flow' ), - ), - array( - 'name' => __( 'Section Editors', 'edit-flow' ), - 'description' => __( 'Providing feedback and direction.', 'edit-flow' ), - ), - ); - foreach( $default_usergroups as $args ) { - $this->add_usergroup( $args ); + 'configure_page_cb' => 'print_configure_view', + 'configure_link_text' => __( 'Manage User Groups', 'edit-flow' ), + 'autoload' => false, + 'settings_help_tab' => array( + 'id' => 'ef-user-groups-overview', + 'title' => __( 'Overview', 'edit-flow' ), + 'content' => __( '

For those with many people involved in the publishing process, user groups helps you keep them organized.

Currently, user groups are primarily used for subscribing a set of users to a post for notifications.

', 'edit-flow' ), + ), + 'settings_help_sidebar' => __( '

For more information:

User Groups Documentation

Edit Flow Forum

Edit Flow on Github

', 'edit-flow' ), + ); + $this->module = EditFlow()->register_module( 'user_groups', $args ); } - - } - /** - * Upgrade our data in case we need to - * - * @since 0.7 - */ - function upgrade( $previous_version ) { - global $edit_flow; + /** + * Module startup + */ - // Upgrade path to v0.7 - if ( version_compare( $previous_version, '0.7' , '<' ) ) { - global $wpdb; + /** + * Initialize the rest of the stuff in the class if the module is active + * + * @since 0.7 + */ + public function init() { - // Set all of the user group terms to our new taxonomy - $wpdb->update( $wpdb->term_taxonomy, array( 'taxonomy' => self::taxonomy_key ), array( 'taxonomy' => 'following_usergroups' ) ); + // Register the objects where we'll be storing data and relationships + $this->register_usergroup_objects(); - // Get all of the users who are a part of user groups and assign them to their new user group values - $query = "SELECT * FROM $wpdb->usermeta WHERE meta_key='wp_ef_usergroups';"; - $usergroup_users = $wpdb->get_results( $query ); + $this->manage_usergroups_cap = apply_filters( 'ef_manage_usergroups_cap', $this->manage_usergroups_cap ); - // Sort all of the users based on their usergroup(s) - $users_to_add = array(); - foreach( (array)$usergroup_users as $usergroup_user ) { - if ( is_object( $usergroup_user ) ) - $users_to_add[$usergroup_user->meta_value][] = (int)$usergroup_user->user_id; - } - // Add user IDs to each usergroup - foreach( $users_to_add as $usergroup_slug => $users_array ) { - $usergroup = $this->get_usergroup_by( 'slug', $usergroup_slug ); - $this->add_users_to_usergroup( $users_array, $usergroup->term_id ); + // Register our settings + add_action( 'admin_init', array( $this, 'register_settings' ) ); + + // Handle any adding, editing or saving + add_action( 'admin_init', array( $this, 'handle_add_usergroup' ) ); + add_action( 'admin_init', array( $this, 'handle_edit_usergroup' ) ); + add_action( 'admin_init', array( $this, 'handle_delete_usergroup' ) ); + add_action( 'wp_ajax_inline_save_usergroup', array( $this, 'handle_ajax_inline_save_usergroup' ) ); + + // Usergroups can be managed from the User profile view + add_action( 'show_user_profile', array( $this, 'user_profile_page' ) ); + add_action( 'edit_user_profile', array( $this, 'user_profile_page' ) ); + add_action( 'user_profile_update_errors', array( $this, 'user_profile_update' ), 10, 3 ); + + // Javascript and CSS if we need it + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); + } + + /** + * Load the capabilities onto users the first time the module is run + * + * @since 0.7 + */ + public function install() { + + // Add necessary capabilities to allow management of user groups + $usergroup_roles = array( + 'administrator' => array( 'edit_usergroups' ), + ); + foreach ( $usergroup_roles as $role => $caps ) { + $this->add_caps_to_role( $role, $caps ); } - // Update the term slugs for each user group - $all_usergroups = $this->get_usergroups(); - foreach( $all_usergroups as $usergroup ) { - $new_slug = str_replace( 'ef_', self::term_prefix, $usergroup->slug ); - $this->update_usergroup( $usergroup->term_id, array( 'slug' => $new_slug ) ); + + // Create our default usergroups + $default_usergroups = array( + array( + 'name' => __( 'Copy Editors', 'edit-flow' ), + 'description' => __( 'Making sure the quality is top-notch.', 'edit-flow' ), + ), + array( + 'name' => __( 'Photographers', 'edit-flow' ), + 'description' => __( 'Capturing the story visually.', 'edit-flow' ), + ), + array( + 'name' => __( 'Reporters', 'edit-flow' ), + 'description' => __( 'Out in the field, writing stories.', 'edit-flow' ), + ), + array( + 'name' => __( 'Section Editors', 'edit-flow' ), + 'description' => __( 'Providing feedback and direction.', 'edit-flow' ), + ), + ); + foreach ( $default_usergroups as $args ) { + $this->add_usergroup( $args ); } + } - // Delete all of the previous usermeta values - $wpdb->query( "DELETE FROM $wpdb->usermeta WHERE meta_key='wp_ef_usergroups';" ); + /** + * Upgrade our data in case we need to + * + * @since 0.7 + */ + public function upgrade( $previous_version ) { + global $edit_flow; - // Technically we've run this code before so we don't want to auto-install new data - $edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); + // Upgrade path to v0.7 + if ( version_compare( $previous_version, '0.7', '<' ) ) { + global $wpdb; - } - // Upgrade path to v0.7.4 - if ( version_compare( $previous_version, '0.7.4', '<' ) ) { - // Usergroup descriptions become base64_encoded, instead of maybe json_encoded. - $this->upgrade_074_term_descriptions( self::taxonomy_key ); + // Set all of the user group terms to our new taxonomy + $wpdb->update( $wpdb->term_taxonomy, array( 'taxonomy' => self::taxonomy_key ), array( 'taxonomy' => 'following_usergroups' ) ); + + // Get all of the users who are a part of user groups and assign them to their new user group values + $query = "SELECT * FROM $wpdb->usermeta WHERE meta_key='wp_ef_usergroups';"; + // There's no userdata here in this query and it's an upgrade query + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $usergroup_users = $wpdb->get_results( $query ); + + // Sort all of the users based on their usergroup(s) + $users_to_add = array(); + foreach ( (array) $usergroup_users as $usergroup_user ) { + if ( is_object( $usergroup_user ) ) { + $users_to_add[ $usergroup_user->meta_value ][] = (int) $usergroup_user->user_id; + } + } + // Add user IDs to each usergroup + foreach ( $users_to_add as $usergroup_slug => $users_array ) { + $usergroup = $this->get_usergroup_by( 'slug', $usergroup_slug ); + $this->add_users_to_usergroup( $users_array, $usergroup->term_id ); + } + // Update the term slugs for each user group + $all_usergroups = $this->get_usergroups(); + foreach ( $all_usergroups as $usergroup ) { + $new_slug = str_replace( 'ef_', self::term_prefix, $usergroup->slug ); + $this->update_usergroup( $usergroup->term_id, array( 'slug' => $new_slug ) ); + } + + // Delete all of the previous usermeta values + $wpdb->query( "DELETE FROM $wpdb->usermeta WHERE meta_key='wp_ef_usergroups';" ); + + // Technically we've run this code before so we don't want to auto-install new data + $edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); + + } + // Upgrade path to v0.7.4 + if ( version_compare( $previous_version, '0.7.4', '<' ) ) { + // Usergroup descriptions become base64_encoded, instead of maybe json_encoded. + $this->upgrade_074_term_descriptions( self::taxonomy_key ); + } } - } - - /** - * Individual Usergroups are stored using a custom taxonomy - * Posts are associated with usergroups based on taxonomy relationship - * User associations are stored serialized in the term's description field - * - * @since 0.7 - * - * @uses register_taxonomy() - */ - function register_usergroup_objects() { - - // Load the currently supported post types so we only register against those - $supported_post_types = $this->get_post_types_for_module( $this->module ); - - // Use a taxonomy to manage relationships between posts and usergroups - $args = array( - 'public' => false, - 'rewrite' => false, - ); - register_taxonomy( self::taxonomy_key, $supported_post_types, $args ); - } - - /** - * Enqueue necessary admin scripts - * - * @since 0.7 - * - * @uses wp_enqueue_script() - */ - function enqueue_admin_scripts() { - - if ( $this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view( $this->module->name ) ) { - wp_enqueue_script( 'jquery-listfilterizer' ); - wp_enqueue_script( 'jquery-quicksearch' ); - wp_enqueue_script( 'edit-flow-user-groups-js', $this->module_url . 'lib/user-groups.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch' ), EDIT_FLOW_VERSION, true ); + /** + * Individual Usergroups are stored using a custom taxonomy + * Posts are associated with usergroups based on taxonomy relationship + * User associations are stored serialized in the term's description field + * + * @since 0.7 + * + * @uses register_taxonomy() + */ + public function register_usergroup_objects() { + + // Load the currently supported post types so we only register against those + $supported_post_types = $this->get_post_types_for_module( $this->module ); + + // Use a taxonomy to manage relationships between posts and usergroups + $args = array( + 'public' => false, + 'rewrite' => false, + ); + register_taxonomy( self::taxonomy_key, $supported_post_types, $args ); } - - if ( $this->is_whitelisted_settings_view( $this->module->name ) ) - wp_enqueue_script( 'edit-flow-user-groups-configure-js', $this->module_url . 'lib/user-groups-configure.js', array( 'jquery' ), EDIT_FLOW_VERSION, true ); - } - - /** - * Enqueue necessary admin styles, but only on the proper pages - * - * @since 0.7 - * - * @uses wp_enqueue_style() - */ - function enqueue_admin_styles() { - - if ( $this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view() ) { - wp_enqueue_style( 'jquery-listfilterizer' ); - wp_enqueue_style( 'edit-flow-user-groups-css', $this->module_url . 'lib/user-groups.css', false, EDIT_FLOW_VERSION ); + /** + * Enqueue necessary admin scripts + * + * @since 0.7 + * + * @uses wp_enqueue_script() + */ + public function enqueue_admin_scripts() { + + if ( $this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view( $this->module->name ) ) { + wp_enqueue_script( 'jquery-listfilterizer' ); + wp_enqueue_script( 'jquery-quicksearch' ); + wp_enqueue_script( 'edit-flow-user-groups-js', $this->module_url . 'lib/user-groups.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch' ), EDIT_FLOW_VERSION, true ); + } + + if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { + wp_enqueue_script( 'edit-flow-user-groups-configure-js', $this->module_url . 'lib/user-groups-configure.js', array( 'jquery' ), EDIT_FLOW_VERSION, true ); + } } - } - - /** - * Module ??? - */ - - /** - * Handles a POST request to add a new Usergroup. Redirects to edit view after - * for admin to add users to usergroup - * Hooked into 'admin_init' and kicks out right away if no action - * - * @since 0.7 - */ - function handle_add_usergroup() { - if ( !isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) - || $_GET['page'] != $this->module->settings_slug || $_POST['form-action'] != 'add-usergroup' ) - return; - - if ( !wp_verify_nonce( $_POST['_wpnonce'], 'add-usergroup' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( $this->manage_usergroups_cap ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - // Sanitize all of the user-entered values - $name = strip_tags( trim( $_POST['name'] ) ); - $description = stripslashes( strip_tags( trim( $_POST['description'] ) ) ); - - $_REQUEST['form-errors'] = array(); - /** - * Form validation for adding new Usergroup + * Enqueue necessary admin styles, but only on the proper pages + * + * @since 0.7 * - * Details - * - 'name' is a required field, but can't match an existing name or slug. Needs to be 40 characters or less - * - "description" can accept a limited amount of HTML, and is optional + * @uses wp_enqueue_style() */ - // Field is required - if ( empty( $name ) ) - $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the user group.', 'edit-flow' ); - // Check to ensure a term with the same name doesn't exist - if ( $this->get_usergroup_by( 'name', $name ) ) - $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); - // Check to ensure a term with the same slug doesn't exist - if ( $this->get_usergroup_by( 'slug', sanitize_title( $name ) ) ) - $_REQUEST['form-errors']['name'] = __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ); - if ( strlen( $name ) > 40 ) - $_REQUEST['form-errors']['name'] = __( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ); - // Kick out if there are any errors - if ( count( $_REQUEST['form-errors'] ) ) { - $_REQUEST['error'] = 'form-error'; - return; + public function enqueue_admin_styles() { + + + if ( $this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view() ) { + wp_enqueue_style( 'jquery-listfilterizer' ); + wp_enqueue_style( 'edit-flow-user-groups-css', $this->module_url . 'lib/user-groups.css', false, EDIT_FLOW_VERSION ); + } } - // Try to add the Usergroup - $args = array( - 'name' => $name, - 'description' => $description, - ); - $usergroup = $this->add_usergroup( $args ); - if ( is_wp_error( $usergroup ) ) - wp_die( __( 'Error adding usergroup.', 'edit-flow' ) ); - - $args = array( - 'action' => 'edit-usergroup', - 'usergroup-id' => $usergroup->term_id, - 'message' => 'usergroup-added' - ); - $redirect_url = $this->get_link( $args ); - wp_redirect( $redirect_url ); - exit; - } - - /** - * Handles a POST request to edit a Usergroup - * Hooked into 'admin_init' and kicks out right away if no action - * - * @since 0.7 - */ - function handle_edit_usergroup() { - if ( !isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) - || $_GET['page'] != $this->module->settings_slug || $_POST['form-action'] != 'edit-usergroup' ) - return; - - if ( !wp_verify_nonce( $_POST['_wpnonce'], 'edit-usergroup' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( $this->manage_usergroups_cap ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - if ( !$existing_usergroup = $this->get_usergroup_by( 'id', (int)$_POST['usergroup_id'] ) ) - wp_die( $this->module->messages['usergroup-missing'] ); - - // Sanitize all of the user-entered values - $name = strip_tags( trim( $_POST['name'] ) ); - $description = stripslashes( strip_tags( trim( $_POST['description'] ) ) ); - - $_REQUEST['form-errors'] = array(); - /** - * Form validation for editing a Usergroup + * Module ??? + */ + + /** + * Handles a POST request to add a new Usergroup. Redirects to edit view after + * for admin to add users to usergroup + * Hooked into 'admin_init' and kicks out right away if no action * - * Details - * - 'name' is a required field, but can't match an existing name or slug. Needs to be 40 characters or less - * - "description" can accept a limited amount of HTML, and is optional + * @since 0.7 */ - // Field is required - if ( empty( $name ) ) - $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the user group.', 'edit-flow' ); - // Check to ensure a term with the same name doesn't exist - $search_term = $this->get_usergroup_by( 'name', $name ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_usergroup->term_id ) - $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); - // Check to ensure a term with the same slug doesn't exist - $search_term = $this->get_usergroup_by( 'slug', sanitize_title( $name ) ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_usergroup->term_id ) - $_REQUEST['form-errors']['name'] = __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ); - if ( strlen( $name ) > 40 ) - $_REQUEST['form-errors']['name'] = __( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ); - // Kick out if there are any errors - if ( count( $_REQUEST['form-errors'] ) ) { - $_REQUEST['error'] = 'form-error'; - return; + public function handle_add_usergroup() { + + if ( ! isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) + || $_GET['page'] != $this->module->settings_slug || 'add-usergroup' != $_POST['form-action'] ) { + return; + } + + if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'add-usergroup' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( $this->manage_usergroups_cap ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + // Sanitize all of the user-entered values + $name = ( isset( $_POST['name'] ) ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; + $description = ( isset( $_POST['description'] ) ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; + + $_REQUEST['form-errors'] = array(); + + /** + * Form validation for adding new Usergroup + * + * Details + * - 'name' is a required field, but can't match an existing name or slug. Needs to be 40 characters or less + * - "description" can accept a limited amount of HTML, and is optional + */ + // Field is required + if ( empty( $name ) ) { + $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the user group.', 'edit-flow' ); + } + // Check to ensure a term with the same name doesn't exist + if ( $this->get_usergroup_by( 'name', $name ) ) { + $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); + } + // Check to ensure a term with the same slug doesn't exist + if ( $this->get_usergroup_by( 'slug', sanitize_title( $name ) ) ) { + $_REQUEST['form-errors']['name'] = __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ); + } + if ( strlen( $name ) > 40 ) { + $_REQUEST['form-errors']['name'] = __( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ); + } + // Kick out if there are any errors + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + if ( count( $_REQUEST['form-errors'] ) ) { + $_REQUEST['error'] = 'form-error'; + return; + } + + // Try to add the Usergroup + $args = array( + 'name' => $name, + 'description' => $description, + ); + $usergroup = $this->add_usergroup( $args ); + if ( is_wp_error( $usergroup ) ) { + wp_die( esc_textarea( __( 'Error adding usergroup.', 'edit-flow' ) ) ); + } + + $args = array( + 'action' => 'edit-usergroup', + 'usergroup-id' => $usergroup->term_id, + 'message' => 'usergroup-added', + ); + $redirect_url = $this->get_link( $args ); + wp_redirect( $redirect_url ); + exit; } - // Try to edit the Usergroup - $args = array( - 'name' => $name, - 'description' => $description, - ); - // Gracefully handle the case where all users have been unsubscribed from the user group - $users = isset( $_POST['usergroup_users'] ) ? (array)$_POST['usergroup_users'] : array(); - $users = array_map( 'intval', $users ); - $usergroup = $this->update_usergroup( $existing_usergroup->term_id, $args, $users ); - if ( is_wp_error( $usergroup ) ) - wp_die( __( 'Error updating user group.', 'edit-flow' ) ); - - $args = array( - 'message' => 'usergroup-updated', - ); - $redirect_url = $this->get_link( $args ); - wp_redirect( $redirect_url ); - exit; - } - - /** - * Handles a request to delete a Usergroup. - * Hooked into 'admin_init' and kicks out right away if no action - * - * @since 0.7 - */ - function handle_delete_usergroup() { - if ( !isset( $_GET['page'], $_GET['action'], $_GET['usergroup-id'] ) - || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'delete-usergroup' ) + /** + * Handles a POST request to edit a Usergroup + * Hooked into 'admin_init' and kicks out right away if no action + * + * @since 0.7 + */ + public function handle_edit_usergroup() { + if ( ! isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) + || $_GET['page'] != $this->module->settings_slug || 'edit-usergroup' != $_POST['form-action'] ) { return; - - if ( !wp_verify_nonce( $_GET['nonce'], 'delete-usergroup' ) ) - wp_die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( $this->manage_usergroups_cap ) ) - wp_die( $this->module->messages['invalid-permissions'] ); - - $result = $this->delete_usergroup( (int)$_GET['usergroup-id'] ); - if ( !$result || is_wp_error( $result ) ) - wp_die( __( 'Error deleting user group.', 'edit-flow' ) ); - - $redirect_url = $this->get_link( array( 'message' => 'usergroup-deleted' ) ); - wp_redirect( $redirect_url ); - exit; - } - - /** - * Handle the request to update a given Usergroup via inline edit - * - * @since 0.7 - */ - function handle_ajax_inline_save_usergroup() { - - if ( !wp_verify_nonce( $_POST['inline_edit'], 'usergroups-inline-edit-nonce' ) ) - die( $this->module->messages['nonce-failed'] ); - - if ( !current_user_can( $this->manage_usergroups_cap ) ) - die( $this->module->messages['invalid-permissions'] ); - - $usergroup_id = (int) $_POST['usergroup_id']; - if ( !$existing_term = $this->get_usergroup_by( 'id', $usergroup_id ) ) - die( $this->module->messages['usergroup-missing'] ); - - $name = strip_tags( trim( $_POST['name'] ) ); - $description = stripslashes( strip_tags( trim( $_POST['description'] ) ) ); - + } + + if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'edit-usergroup' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( $this->manage_usergroups_cap ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + $usergroup_id = isset( $_POST['usergroup_id'] ) ? (int) $_POST['usergroup_id'] : 0; + $existing_usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); + if ( ! $existing_usergroup ) { + wp_die( esc_html( $this->module->messages['usergroup-missing'] ) ); + } + + // Sanitize all of the user-entered values + $name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; + $description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; + + $_REQUEST['form-errors'] = array(); + + /** + * Form validation for editing a Usergroup + * + * Details + * - 'name' is a required field, but can't match an existing name or slug. Needs to be 40 characters or less + * - "description" can accept a limited amount of HTML, and is optional + */ + // Field is required + if ( empty( $name ) ) { + $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the user group.', 'edit-flow' ); + } + // Check to ensure a term with the same name doesn't exist + $search_term = $this->get_usergroup_by( 'name', $name ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_usergroup->term_id ) { + $_REQUEST['form-errors']['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); + } + // Check to ensure a term with the same slug doesn't exist + $search_term = $this->get_usergroup_by( 'slug', sanitize_title( $name ) ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_usergroup->term_id ) { + $_REQUEST['form-errors']['name'] = __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ); + } + if ( strlen( $name ) > 40 ) { + $_REQUEST['form-errors']['name'] = __( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ); + } + // Kick out if there are any errors + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + if ( count( $_REQUEST['form-errors'] ) ) { + $_REQUEST['error'] = 'form-error'; + return; + } + + // Try to edit the Usergroup + $args = array( + 'name' => $name, + 'description' => $description, + ); + // Gracefully handle the case where all users have been unsubscribed from the user group + $users = isset( $_POST['usergroup_users'] ) ? (array) $_POST['usergroup_users'] : array(); + $users = array_map( 'intval', $users ); + $usergroup = $this->update_usergroup( $existing_usergroup->term_id, $args, $users ); + if ( is_wp_error( $usergroup ) ) { + wp_die( esc_textarea( __( 'Error updating user group.', 'edit-flow' ) ) ); + } + + $args = array( + 'message' => 'usergroup-updated', + ); + $redirect_url = $this->get_link( $args ); + wp_redirect( $redirect_url ); + exit; + } + /** - * Form validation for editing Usergroup - */ - // Check if name field was filled in - if ( empty( $name ) ) { - $change_error = new WP_Error( 'invalid', __( 'Please enter a name for the user group.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); + * Handles a request to delete a Usergroup. + * Hooked into 'admin_init' and kicks out right away if no action + * + * @since 0.7 + */ + public function handle_delete_usergroup() { + if ( ! isset( $_GET['page'], $_GET['action'], $_GET['usergroup-id'] ) + || $_GET['page'] != $this->module->settings_slug || 'delete-usergroup' != $_GET['action'] ) { + return; + } + + if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'delete-usergroup' ) ) { + wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( $this->manage_usergroups_cap ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + $result = $this->delete_usergroup( (int) $_GET['usergroup-id'] ); + if ( ! $result || is_wp_error( $result ) ) { + wp_die( esc_html__( 'Error deleting user group.', 'edit-flow' ) ); + } + + $redirect_url = $this->get_link( array( 'message' => 'usergroup-deleted' ) ); + wp_redirect( $redirect_url ); + exit; } - // Check that the name doesn't exceed 40 chars - if ( strlen( $name ) > 40 ) { - $change_error = new WP_Error( 'invalid', __( 'User group name cannot exceed 40 characters. Please try a shorter name.' ) ); - die( $change_error->get_error_message() ); + + /** + * Handle the request to update a given Usergroup via inline edit + * + * @since 0.7 + */ + public function handle_ajax_inline_save_usergroup() { + + if ( ! isset( $_POST['inline_edit'] ) || ! wp_verify_nonce( $_POST['inline_edit'], 'usergroups-inline-edit-nonce' ) ) { + die( esc_html( $this->module->messages['nonce-failed'] ) ); + } + + if ( ! current_user_can( $this->manage_usergroups_cap ) ) { + die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } + + $usergroup_id = isset( $_POST['usergroup_id'] ) ? (int) $_POST['usergroup_id'] : 0; + if ( ! $existing_term = $this->get_usergroup_by( 'id', $usergroup_id ) ) { + die( esc_html( $this->module->messages['usergroup-missing'] ) ); + } + + $name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; + $description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; + + /** + * Form validation for editing Usergroup + */ + // Check if name field was filled in + if ( empty( $name ) ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Please enter a name for the user group.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } + // Check that the name doesn't exceed 40 chars + if ( strlen( $name ) > 40 ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'User group name cannot exceed 40 characters. Please try a shorter name.' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } + // Check to ensure a term with the same name doesn't exist + $search_term = $this->get_usergroup_by( 'name', $name ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Name already in use. Please choose another.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } + // Check to ensure a term with the same slug doesn't exist + $search_term = $this->get_usergroup_by( 'slug', sanitize_title( $name ) ); + if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { + $change_error = new WP_Error( 'invalid', esc_html__( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ) ); + die( esc_html( $change_error->get_error_message() ) ); + } + + // Prepare the term name and description for saving + $args = array( + 'name' => $name, + 'description' => $description, + ); + $return = $this->update_usergroup( $existing_term->term_id, $args ); + if ( ! is_wp_error( $return ) ) { + set_current_screen( 'edit-usergroup' ); + $wp_list_table = new EF_Usergroups_List_Table(); + $wp_list_table->prepare_items(); + echo wp_kses_post( $wp_list_table->single_row( $return ) ); + die(); + } else { + // translators: %s is the name of the user group + $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the user group: %s', 'edit-flow' ), $name ) ); + die( esc_html( $change_error->get_error_message() ) ); + } } - // Check to ensure a term with the same name doesn't exist - $search_term = $this->get_usergroup_by( 'name', $name ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { - $change_error = new WP_Error( 'invalid', __( 'Name already in use. Please choose another.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); + + /** + * Register settings for notifications so we can partially use the Settings API + * (We use the Settings API for form generation, but not saving) + * + * @since 0.7 + * @uses add_settings_section(), add_settings_field() + */ + public function register_settings() { + add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); + add_settings_field( 'post_types', __( 'Add to these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' ); } - // Check to ensure a term with the same slug doesn't exist - $search_term = $this->get_usergroup_by( 'slug', sanitize_title( $name ) ); - if ( is_object( $search_term ) && $search_term->term_id != $existing_term->term_id ) { - $change_error = new WP_Error( 'invalid', __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ) ); - die( $change_error->get_error_message() ); + + /** + * Choose the post types for Usergroups + * + * @since 0.7 + */ + public function settings_post_types_option() { + global $edit_flow; + $edit_flow->settings->helper_option_custom_post_type( $this->module ); } - - // Prepare the term name and description for saving - $args = array( - 'name' => $name, - 'description' => $description, - ); - $return = $this->update_usergroup( $existing_term->term_id, $args ); - if( !is_wp_error( $return ) ) { - set_current_screen( 'edit-usergroup' ); - $wp_list_table = new EF_Usergroups_List_Table(); - $wp_list_table->prepare_items(); - echo $wp_list_table->single_row( $return ); - die(); - } else { - $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the user group: %s', 'edit-flow' ), $usergroup_name ) ); - die( $change_error->get_error_message() ); + + /** + * Validate data entered by the user + * + * @since 0.7 + * + * @param array $new_options New values that have been entered by the user + * @return array $new_options Form values after they've been sanitized + */ + public function settings_validate( $new_options ) { + + + // Whitelist validation for the post type options + if ( ! isset( $new_options['post_types'] ) ) { + $new_options['post_types'] = array(); + } + $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); + + return $new_options; } - - } - - /** - * Register settings for notifications so we can partially use the Settings API - * (We use the Settings API for form generation, but not saving) - * - * @since 0.7 - * @uses add_settings_section(), add_settings_field() - */ - function register_settings() { - add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); - add_settings_field( 'post_types', __( 'Add to these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' ); - } - - /** - * Choose the post types for Usergroups - * - * @since 0.7 - */ - function settings_post_types_option() { - global $edit_flow; - $edit_flow->settings->helper_option_custom_post_type( $this->module ); - } - /** - * Validate data entered by the user - * - * @since 0.7 - * - * @param array $new_options New values that have been entered by the user - * @return array $new_options Form values after they've been sanitized - */ - function settings_validate( $new_options ) { - - - // Whitelist validation for the post type options - if ( !isset( $new_options['post_types'] ) ) - $new_options['post_types'] = array(); - $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); - - return $new_options; - } - - /** - * Build a configuration view so we can manage our usergroups - * - * @since 0.7 - */ - function print_configure_view() { - global $edit_flow; - - if ( isset( $_GET['action'], $_GET['usergroup-id'] ) && $_GET['action'] == 'edit-usergroup' ) : - /** Full page width view for editing a given usergroup **/ - // Check whether the usergroup exists - $usergroup_id = (int)$_GET['usergroup-id']; - $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); - if ( !$usergroup ) { - echo '

' . $this->module->messages['usergroup-missing'] . '

'; - return; - } - $name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $usergroup->name; - $description = ( isset( $_POST['description'] ) ) ? stripslashes( $_POST['description'] ) : $usergroup->description; - ?> -
+ /** + * Build a configuration view so we can manage our usergroups + * + * @since 0.7 + * Disabling nonce verification because that is not available here, it's just rendering it. The actual save is done in helper_settings_validate_and_save and that's guarded well. + * phpcs:disable:WordPress.Security.NonceVerification.Missing + */ + public function print_configure_view() { + global $edit_flow; + + if ( isset( $_GET['action'], $_GET['usergroup-id'] ) && 'edit-usergroup' == $_GET['action'] ) : + /** Full page width view for editing a given usergroup **/ + // Check whether the usergroup exists + $usergroup_id = (int) $_GET['usergroup-id']; + $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); + if ( ! $usergroup ) { + echo '

' . esc_html( $this->module->messages['usergroup-missing'] ) . '

'; + return; + } + $name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $usergroup->name; + $description = ( isset( $_POST['description'] ) ) ? strip_tags( stripslashes( $_POST['description'] ) ) : $usergroup->description; + ?> +

- 'ef-post_following_list', - 'input_id' => 'usergroup_users' + 'input_id' => 'usergroup_users', ); - ?> - users_select_form( $usergroup->user_ids , $select_form_args ); ?> + ?> + users_select_form( $usergroup->user_ids, $select_form_args ); ?>
-
+
- + ?>
@@ -579,83 +619,98 @@ function print_configure_view() { settings->helper_print_error_or_description( 'description', __( 'The description is primarily for administrative use, to give you some context on what the user group is to be used for.', 'edit-flow' ) ); ?>

- +

- - prepare_items(); - ?> -
display(); ?>
- +
module->options_group_name ); ?> - module->options_group_name ); ?> + module->options_group_name ); ?> module->name ) . '" />'; ?>
- - + +
- + settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the user group.', 'edit-flow' ) ); ?>
- + settings->helper_print_error_or_description( 'description', __( 'The description is primarily for administrative use, to give you some context on what the user group is to be used for.', 'edit-flow' ) ); ?>
'; ?> -

+

- inline_edit(); ?> - manage_usergroups_cap ) ) - return; - - //Don't allow display of user groups from network - if ( ( !is_null( get_current_screen() ) ) && ( get_current_screen()->is_network ) ) - return; - - // Assemble all necessary data - $usergroups = $this->get_usergroups(); - $selected_usergroups = $this->get_usergroups_for_user( $user_id ); - $usergroups_form_args = array( 'input_id' => 'ef_usergroups' ); - ?> + inline_edit(); ?> + manage_usergroups_cap ) ) { + return; + } + + //Don't allow display of user groups from network + if ( ( ! is_null( get_current_screen() ) ) && ( get_current_screen()->is_network ) ) { + return; + } + + // Assemble all necessary data + $usergroups = $this->get_usergroups(); + $selected_usergroups = $this->get_usergroups_for_user( $user_id ); + $usergroups_form_args = array( 'input_id' => 'ef_usergroups' ); + ?>
-

- ID ) : ?> -

+

+ ID === $user_id ) : ?> +

-

+

@@ -667,571 +722,600 @@ function user_profile_page() {
- - + is_network ) { - return; + /** + * Function called when a user's profile is updated + * Adds user to specified usergroups + * + * @since 0.7 + * + * @param ??? + * @param ??? + * @param ??? + * @return ??? + */ + public function user_profile_update( $errors, $update, $user ) { + + if ( ! $update ) { + return array( &$errors, $update, &$user ); } - } - if ( current_user_can( $this->manage_usergroups_cap ) && wp_verify_nonce( $_POST['ef_edit_profile_usergroups_nonce'], 'ef_edit_profile_usergroups_nonce' ) ) { - // Sanitize the data and save - // Gracefully handle the case where the user was unsubscribed from all usergroups - $usergroups = isset( $_POST['ef_usergroups'] ) ? array_map( 'intval', (array)$_POST['ef_usergroups'] ) : array(); - $all_usergroups = $this->get_usergroups(); - foreach( $all_usergroups as $usergroup ) { - if ( in_array( $usergroup->term_id, $usergroups ) ) - $this->add_user_to_usergroup( $user->ID, $usergroup->term_id ); - else - $this->remove_user_from_usergroup( $user->ID, $usergroup->term_id ); + // `get_current_screen()` is defined on most admin pages, but not all. + if ( function_exists( 'get_current_screen' ) ) { + //Don't allow update of user groups from network + $screen = get_current_screen(); + if ( ! is_null( $screen ) && $screen->is_network ) { + return; + } + } + + if ( isset( $_POST['ef_edit_profile_usergroups_nonce'] ) && current_user_can( $this->manage_usergroups_cap ) && wp_verify_nonce( $_POST['ef_edit_profile_usergroups_nonce'], 'ef_edit_profile_usergroups_nonce' ) ) { + // Sanitize the data and save + // Gracefully handle the case where the user was unsubscribed from all usergroups + $usergroups = isset( $_POST['ef_usergroups'] ) ? array_map( 'intval', (array) $_POST['ef_usergroups'] ) : array(); + $all_usergroups = $this->get_usergroups(); + foreach ( $all_usergroups as $usergroup ) { + if ( in_array( $usergroup->term_id, $usergroups ) ) { + $this->add_user_to_usergroup( $user->ID, $usergroup->term_id ); + } else { + $this->remove_user_from_usergroup( $user->ID, $usergroup->term_id ); + } + } } + + return array( &$errors, $update, &$user ); } - - return array( &$errors, $update, &$user ); - } - - /** - * Generate a link to one of the usergroups actions - * - * @since 0.7 - * - * @param string $action Action we want the user to take - * @param array $args Any query args to add to the URL - * @return string $link Direct link to delete a usergroup - */ - function get_link( $args = array() ) { - if ( !isset( $args['action'] ) ) - $args['action'] = ''; - if ( !isset( $args['page'] ) ) - $args['page'] = $this->module->settings_slug; - // Add other things we may need depending on the action - switch( $args['action'] ) { - case 'delete-usergroup': - $args['nonce'] = wp_create_nonce( $args['action'] ); - break; - default: - break; + + /** + * Generate a link to one of the usergroups actions + * + * @since 0.7 + * + * @param string $action Action we want the user to take + * @param array $args Any query args to add to the URL + * @return string $link Direct link to delete a usergroup + */ + public function get_link( $args = array() ) { + if ( ! isset( $args['action'] ) ) { + $args['action'] = ''; + } + if ( ! isset( $args['page'] ) ) { + $args['page'] = $this->module->settings_slug; + } + // Add other things we may need depending on the action + switch ( $args['action'] ) { + case 'delete-usergroup': + $args['nonce'] = wp_create_nonce( $args['action'] ); + break; + default: + break; + } + return add_query_arg( $args, get_admin_url( null, 'admin.php' ) ); } - return add_query_arg( $args, get_admin_url( null, 'admin.php' ) ); - } - - /** - * Displays a list of usergroups with checkboxes - * - * @since 0.7 - * - * @param array $selected List of usergroup keys that should be checked - * @param array $args ??? - */ - function usergroups_select_form( $selected = array(), $args = null ) { - - // TODO add $args for additional options - // e.g. showing members assigned to group (John Smith, Jane Doe, and 9 others) - // before , after , class, id names? - $defaults = array( - 'list_class' => 'ef-post_following_list', - 'list_id' => 'ef-following_usergroups', - 'input_id' => 'following_usergroups' - ); - - $parsed_args = wp_parse_args( $args, $defaults ); - extract( $parsed_args, EXTR_SKIP ); - $usergroups = $this->get_usergroups(); - if ( empty($usergroups) ) { - ?> -

- -
    - term_id, $selected ) ) ? ' checked="checked"' : ''; + /** + * Displays a list of usergroups with checkboxes + * + * @since 0.7 + * + * @param array $selected List of usergroup keys that should be checked + * @param array $args ??? + */ + public function usergroups_select_form( $selected = array(), $args = null ) { + + // TODO add $args for additional options + // e.g. showing members assigned to group (John Smith, Jane Doe, and 9 others) + // before , after , class, id names? + $defaults = array( + 'list_class' => 'ef-post_following_list', + 'list_id' => 'ef-following_usergroups', + 'input_id' => 'following_usergroups', + ); + + $parsed_args = wp_parse_args( $args, $defaults ); + extract( $parsed_args, EXTR_SKIP ); + $usergroups = $this->get_usergroups(); + if ( empty( $usergroups ) ) { ?> +

    + +
      + term_id, $selected ) ) ? ' checked="checked"' : ''; + ?>
    • -
    • + +
    -
- get_usergroup_by( 'id', $usergroup_term->term_id ); } - return $usergroups; - } - - /** - * Get all of the data associated with a single usergroup - * Usergroup contains: - * - ID (key = term_id) - * - Slug (prefixed with our special key to avoid conflicts) - * - Name - * - Description - * - User IDs (array of IDs) - * - * @since 0.7 - * - * @param string $field 'id', 'name', or 'slug' - * @param int|string $value Value for the search field - * @return object|array|WP_Error $usergroup Usergroup information as specified by $output - */ - function get_usergroup_by( $field, $value ) { - - $usergroup = get_term_by( $field, $value, self::taxonomy_key ); - - if ( !$usergroup || is_wp_error( $usergroup ) ) - return $usergroup; - - // We're using an encoded description field to store extra values - // Declare $user_ids ahead of time just in case it's empty - $usergroup->user_ids = array(); - $unencoded_description = $this->get_unencoded_description( $usergroup->description ); - if ( is_array( $unencoded_description ) ) { - foreach( $unencoded_description as $key => $value ) { - $usergroup->$key = $value; + + /** + * Core Usergroups Module Functionality + */ + + /** + * Get all of the registered usergroups. Returns an array of objects + * + * @since 0.7 + * + * @param array $args Arguments to filter/sort by + * @return array|bool $usergroups Array of Usergroups with relevant data, false if none + */ + public function get_usergroups( $args = array() ) { + + // We want empty terms by default + $usergroup_terms = get_terms( array( + 'taxonomy' => self::taxonomy_key, + 'hide_empty' => isset( $args['hide_empty'] ), + )); + if ( ! $usergroup_terms ) { + return false; } + + // Run the usergroups through get_usergroup_by() so we load users too + $usergroups = array(); + foreach ( $usergroup_terms as $usergroup_term ) { + $usergroups[] = $this->get_usergroup_by( 'id', $usergroup_term->term_id ); + } + return $usergroups; } - $usergroup = apply_filters( 'ef_usergroup_object', $usergroup ); + /** + * Get all of the data associated with a single usergroup + * Usergroup contains: + * - ID (key = term_id) + * - Slug (prefixed with our special key to avoid conflicts) + * - Name + * - Description + * - User IDs (array of IDs) + * + * @since 0.7 + * + * @param string $field 'id', 'name', or 'slug' + * @param int|string $value Value for the search field + * @return object|array|WP_Error $usergroup Usergroup information as specified by $output + */ + public function get_usergroup_by( $field, $value ) { + + $usergroup = get_term_by( $field, $value, self::taxonomy_key ); + + if ( ! $usergroup || is_wp_error( $usergroup ) ) { + return $usergroup; + } + + // We're using an encoded description field to store extra values + // Declare $user_ids ahead of time just in case it's empty + $usergroup->user_ids = array(); + $unencoded_description = $this->get_unencoded_description( $usergroup->description ); + if ( is_array( $unencoded_description ) ) { + foreach ( $unencoded_description as $key => $value ) { + $usergroup->$key = $value; + } + } + + $usergroup = apply_filters( 'ef_usergroup_object', $usergroup ); - return $usergroup; - } - - /** - * Create a new usergroup containing: - * - Name - * - Slug (prefixed with our special key to avoid conflicts) - * - Description - * - Users - * - * @since 0.7 - * - * @param array $args Name (optional), slug and description for the usergroup - * @param array $user_ids IDs for the users to be added to the Usergroup - * @return object|WP_Error $usergroup Object for the new Usergroup on success, WP_Error otherwise - */ - function add_usergroup( $args = array(), $user_ids = array() ) { - - if ( !isset( $args['name'] ) ) - return new WP_Error( 'invalid', __( 'New user groups must have a name', 'edit-flow' ) ); - - $name = $args['name']; - $default = array( - 'name' => '', - 'slug' => self::term_prefix . sanitize_title( $name ), - 'description' => '', - ); - $args = array_merge( $default, $args ); - - // Encode our extra fields and then store them in the description field - $args_to_encode = array( - 'description' => $args['description'], - 'user_ids' => array_unique( $user_ids ), - ); - $encoded_description = $this->get_encoded_description( $args_to_encode ); - $args['description'] = $encoded_description; - $usergroup = wp_insert_term( $name, self::taxonomy_key, $args ); - if ( is_wp_error( $usergroup ) ) - return $usergroup; - - return $this->get_usergroup_by( 'id', $usergroup['term_id'] ); - } - - /** - * Update a usergroup with new data. - * Fields can include: - * - Name - * - Slug (prefixed with our special key, of course) - * - Description - * - Users - * - * @since 0.7 - * - * @param int $id Unique ID for the usergroup - * @param array $args Usergroup meta to update (name, slug, description) - * @param array $users Users to be added to the Usergroup. If set, removes existing users first. - * @return object|WP_Error $usergroup Object for the updated Usergroup on success, WP_Error otherwise - */ - function update_usergroup( $id, $args = array(), $users = null ) { - - $existing_usergroup = $this->get_usergroup_by( 'id', $id ); - if ( is_wp_error( $existing_usergroup ) ) - return new WP_Error( 'invalid', __( "User group doesn't exist.", 'edit-flow' ) ); - - // Encode our extra fields and then store them in the description field - $args_to_encode = array(); - $args_to_encode['description'] = ( isset( $args['description'] ) ) ? $args['description'] : $existing_usergroup->description; - $args_to_encode['user_ids'] = ( is_array( $users ) ) ? $users : $existing_usergroup->user_ids; - $args_to_encode['user_ids'] = array_unique( $args_to_encode['user_ids'] ); - $encoded_description = $this->get_encoded_description( $args_to_encode ); - $args['description'] = $encoded_description; - - $usergroup = wp_update_term( $id, self::taxonomy_key, $args ); - if ( is_wp_error( $usergroup ) ) return $usergroup; - - return $this->get_usergroup_by( 'id', $usergroup['term_id'] ); - } - - /** - * Delete a usergroup based on its term ID - * - * @since 0.7 - * - * @param int $id Unique ID for the Usergroup - * @param bool|WP_Error Returns true on success, WP_Error on failure - */ - function delete_usergroup( $id ) { - - $retval = wp_delete_term( $id, self::taxonomy_key ); - return $retval; - } - - /** - * Add an array of user logins or IDs to a given usergroup - * - * @since 0.7 - * - * @param array $user_ids_or_logins User IDs or logins to be added to the usergroup - * @param int $id Usergroup to perform the action on - * @param bool $reset Delete all of the relationships before adding - * @return bool $success Whether or not we were successful - */ - function add_users_to_usergroup( $user_ids_or_logins, $id, $reset = true ) { - - if ( !is_array( $user_ids_or_logins ) ) - return new WP_Error( 'invalid', __( "Invalid users variable. Should be array.", 'edit-flow' ) ); - - // To dump the existing users from a usergroup, we need to pass an empty array - $usergroup = $this->get_usergroup_by( 'id', $id ); - if ( $reset ) { - $retval = $this->update_usergroup( $id, null, array() ); - if ( is_wp_error( $retval ) ) - return $retval; } - - // Add the new users one by one to an array we'll pass back to the usergroup - $new_users = array(); - foreach ( (array)$user_ids_or_logins as $user_id_or_login ) { - if ( !is_numeric( $user_id_or_login ) ) - $new_users[] = get_user_by( 'login', $user_id_or_login )->ID; - else - $new_users[] = (int)$user_id_or_login; + + /** + * Create a new usergroup containing: + * - Name + * - Slug (prefixed with our special key to avoid conflicts) + * - Description + * - Users + * + * @since 0.7 + * + * @param array $args Name (optional), slug and description for the usergroup + * @param array $user_ids IDs for the users to be added to the Usergroup + * @return object|WP_Error $usergroup Object for the new Usergroup on success, WP_Error otherwise + */ + public function add_usergroup( $args = array(), $user_ids = array() ) { + + if ( ! isset( $args['name'] ) ) { + return new WP_Error( 'invalid', __( 'New user groups must have a name', 'edit-flow' ) ); + } + + $name = $args['name']; + $default = array( + 'name' => '', + 'slug' => self::term_prefix . sanitize_title( $name ), + 'description' => '', + ); + $args = array_merge( $default, $args ); + + // Encode our extra fields and then store them in the description field + $args_to_encode = array( + 'description' => $args['description'], + 'user_ids' => array_unique( $user_ids ), + ); + $encoded_description = $this->get_encoded_description( $args_to_encode ); + $args['description'] = $encoded_description; + $usergroup = wp_insert_term( $name, self::taxonomy_key, $args ); + if ( is_wp_error( $usergroup ) ) { + return $usergroup; + } + + return $this->get_usergroup_by( 'id', $usergroup['term_id'] ); + } + + /** + * Update a usergroup with new data. + * Fields can include: + * - Name + * - Slug (prefixed with our special key, of course) + * - Description + * - Users + * + * @since 0.7 + * + * @param int $id Unique ID for the usergroup + * @param array $args Usergroup meta to update (name, slug, description) + * @param array $users Users to be added to the Usergroup. If set, removes existing users first. + * @return object|WP_Error $usergroup Object for the updated Usergroup on success, WP_Error otherwise + */ + public function update_usergroup( $id, $args = array(), $users = null ) { + + $existing_usergroup = $this->get_usergroup_by( 'id', $id ); + if ( is_wp_error( $existing_usergroup ) ) { + return new WP_Error( 'invalid', __( "User group doesn't exist.", 'edit-flow' ) ); + } + + // Encode our extra fields and then store them in the description field + $args_to_encode = array(); + $args_to_encode['description'] = ( isset( $args['description'] ) ) ? $args['description'] : $existing_usergroup->description; + $args_to_encode['user_ids'] = ( is_array( $users ) ) ? $users : $existing_usergroup->user_ids; + $args_to_encode['user_ids'] = array_unique( $args_to_encode['user_ids'] ); + $encoded_description = $this->get_encoded_description( $args_to_encode ); + $args['description'] = $encoded_description; + + $usergroup = wp_update_term( $id, self::taxonomy_key, $args ); + if ( is_wp_error( $usergroup ) ) { + return $usergroup; + } + + return $this->get_usergroup_by( 'id', $usergroup['term_id'] ); } - $retval = $this->update_usergroup( $id, null, $new_users ); - if ( is_wp_error( $retval ) ) + + /** + * Delete a usergroup based on its term ID + * + * @since 0.7 + * + * @param int $id Unique ID for the Usergroup + * @param bool|WP_Error Returns true on success, WP_Error on failure + */ + public function delete_usergroup( $id ) { + + $retval = wp_delete_term( $id, self::taxonomy_key ); return $retval; - return true; - } - - /** - * Add a given user to a Usergroup. Can use User ID or user login - * - * @since 0.7 - * - * @param int|string $user_id_or_login User ID or login to be added to the Usergroups - * @param int|array $ids ID for the Usergroup(s) - * @return bool|WP_Error $retval Return true on success, WP_Error on error - */ - function add_user_to_usergroup( $user_id_or_login, $ids ) { - - if ( !is_numeric( $user_id_or_login ) ) - $user_id = get_user_by( 'login', $user_id_or_login )->ID; - else - $user_id = (int)$user_id_or_login; - - foreach( (array)$ids as $usergroup_id ) { - $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); - $usergroup->user_ids[] = $user_id; - $retval = $this->update_usergroup( $usergroup_id, null, $usergroup->user_ids ); - if ( is_wp_error( $retval ) ) - return $retval; } - return true; - } - - /** - * Remove a given user from one or more usergroups - * - * @since 0.7 - * - * @param int|string $user_id_or_login User ID or login to be removed from the Usergroups - * @param int|array $ids ID for the Usergroup(s) - * @return bool|WP_Error $retval Return true on success, WP_Error on error - */ - function remove_user_from_usergroup( $user_id_or_login, $ids ) { - - if ( !is_numeric( $user_id_or_login ) ) - $user_id = get_user_by( 'login', $user_id_or_login )->ID; - else - $user_id = (int)$user_id_or_login; - - // Remove the user from each usergroup specified - foreach( (array)$ids as $usergroup_id ) { - $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); - // @todo I bet there's a PHP function for this I couldn't look up at 35,000 over the Atlantic - foreach( $usergroup->user_ids as $key => $usergroup_user_id ) { - if ( $usergroup_user_id == $user_id ) - unset( $usergroup->user_ids[$key] ); - } - $retval = $this->update_usergroup( $usergroup_id, null, $usergroup->user_ids ); - if ( is_wp_error( $retval ) ) + + /** + * Add an array of user logins or IDs to a given usergroup + * + * @since 0.7 + * + * @param array $user_ids_or_logins User IDs or logins to be added to the usergroup + * @param int $id Usergroup to perform the action on + * @param bool $reset Delete all of the relationships before adding + * @return bool $success Whether or not we were successful + */ + public function add_users_to_usergroup( $user_ids_or_logins, $id, $reset = true ) { + + if ( ! is_array( $user_ids_or_logins ) ) { + return new WP_Error( 'invalid', __( 'Invalid users variable. Should be array.', 'edit-flow' ) ); + } + + // To dump the existing users from a usergroup, we need to pass an empty array + $usergroup = $this->get_usergroup_by( 'id', $id ); + if ( $reset ) { + $retval = $this->update_usergroup( $id, null, array() ); + if ( is_wp_error( $retval ) ) { + return $retval; + } + } + + // Add the new users one by one to an array we'll pass back to the usergroup + $new_users = array(); + foreach ( (array) $user_ids_or_logins as $user_id_or_login ) { + if ( ! is_numeric( $user_id_or_login ) ) { + $new_users[] = get_user_by( 'login', $user_id_or_login )->ID; + } else { + $new_users[] = (int) $user_id_or_login; + } + } + $retval = $this->update_usergroup( $id, null, $new_users ); + if ( is_wp_error( $retval ) ) { return $retval; + } + return true; } - return true; - - } - - /** - * Get all of the Usergroup ids or objects for a given user - * - * @since 0.7 - * - * @param int|string $user_id_or_login User ID or login to search against - * @param array $ids_or_objects Whether to retrieve an array of IDs or usergroup objects - * @param array|bool $usergroup_objects_or_ids Array of usergroup 'ids' or 'objects', false if none - */ - function get_usergroups_for_user( $user_id_or_login, $ids_or_objects = 'ids' ) { - - if ( !is_numeric( $user_id_or_login ) ) - $user_id = get_user_by( 'login', $user_id_or_login )->ID; - else - $user_id = (int)$user_id_or_login; - - // Unfortunately, the easiest way to do this is get all usergroups - // and then loop through each one to see if the user ID is stored - $all_usergroups = $this->get_usergroups(); - if ( !empty( $all_usergroups) ) { - $usergroup_objects_or_ids = array(); - foreach( $all_usergroups as $usergroup ) { - // Not in this usergroup, so keep going - if ( !in_array( $user_id, $usergroup->user_ids ) ) - continue; - if ( $ids_or_objects == 'ids' ) - $usergroup_objects_or_ids[] = (int)$usergroup->term_id; - else if ( $ids_or_objects == 'objects' ) - $usergroup_objects_or_ids[] = $usergroup; - } - return $usergroup_objects_or_ids; - } else { - return false; + + /** + * Add a given user to a Usergroup. Can use User ID or user login + * + * @since 0.7 + * + * @param int|string $user_id_or_login User ID or login to be added to the Usergroups + * @param int|array $ids ID for the Usergroup(s) + * @return bool|WP_Error $retval Return true on success, WP_Error on error + */ + public function add_user_to_usergroup( $user_id_or_login, $ids ) { + + if ( ! is_numeric( $user_id_or_login ) ) { + $user_id = get_user_by( 'login', $user_id_or_login )->ID; + } else { + $user_id = (int) $user_id_or_login; + } + + foreach ( (array) $ids as $usergroup_id ) { + $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); + $usergroup->user_ids[] = $user_id; + $retval = $this->update_usergroup( $usergroup_id, null, $usergroup->user_ids ); + if ( is_wp_error( $retval ) ) { + return $retval; + } + } + return true; + } + + /** + * Remove a given user from one or more usergroups + * + * @since 0.7 + * + * @param int|string $user_id_or_login User ID or login to be removed from the Usergroups + * @param int|array $ids ID for the Usergroup(s) + * @return bool|WP_Error $retval Return true on success, WP_Error on error + */ + public function remove_user_from_usergroup( $user_id_or_login, $ids ) { + + if ( ! is_numeric( $user_id_or_login ) ) { + $user_id = get_user_by( 'login', $user_id_or_login )->ID; + } else { + $user_id = (int) $user_id_or_login; + } + + // Remove the user from each usergroup specified + foreach ( (array) $ids as $usergroup_id ) { + $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); + // @todo I bet there's a PHP function for this I couldn't look up at 35,000 over the Atlantic + foreach ( $usergroup->user_ids as $key => $usergroup_user_id ) { + if ( $usergroup_user_id == $user_id ) { + unset( $usergroup->user_ids[ $key ] ); + } + } + $retval = $this->update_usergroup( $usergroup_id, null, $usergroup->user_ids ); + if ( is_wp_error( $retval ) ) { + return $retval; + } + } + return true; + } + + /** + * Get all of the Usergroup ids or objects for a given user + * + * @since 0.7 + * + * @param int|string $user_id_or_login User ID or login to search against + * @param array $ids_or_objects Whether to retrieve an array of IDs or usergroup objects + * @param array|bool $usergroup_objects_or_ids Array of usergroup 'ids' or 'objects', false if none + */ + public function get_usergroups_for_user( $user_id_or_login, $ids_or_objects = 'ids' ) { + + if ( ! is_numeric( $user_id_or_login ) ) { + $user_id = get_user_by( 'login', $user_id_or_login )->ID; + } else { + $user_id = (int) $user_id_or_login; + } + + // Unfortunately, the easiest way to do this is get all usergroups + // and then loop through each one to see if the user ID is stored + $all_usergroups = $this->get_usergroups(); + if ( ! empty( $all_usergroups ) ) { + $usergroup_objects_or_ids = array(); + foreach ( $all_usergroups as $usergroup ) { + // Not in this usergroup, so keep going + if ( ! in_array( $user_id, $usergroup->user_ids ) ) { + continue; + } + if ( 'ids' == $ids_or_objects ) { + $usergroup_objects_or_ids[] = (int) $usergroup->term_id; + } else if ( 'objects' == $ids_or_objects ) { + $usergroup_objects_or_ids[] = $usergroup; + } + } + return $usergroup_objects_or_ids; + } else { + return false; + } } } - -} } +// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound -if ( !class_exists( 'EF_Usergroups_List_Table' ) ) { -/** - * Usergroups uses WordPress' List Table API for generating the Usergroup management table - * - * @since 0.7 - */ -class EF_Usergroups_List_Table extends WP_List_Table -{ - - var $callback_args; - - function __construct() { - - parent::__construct( array( - 'plural' => 'user groups', - 'singular' => 'user group', - 'ajax' => true - ) ); - - } - +if ( ! class_exists( 'EF_Usergroups_List_Table' ) ) { /** - * @todo Paginate if we have a lot of usergroups + * Usergroups uses WordPress' List Table API for generating the Usergroup management table * * @since 0.7 */ - function prepare_items() { - global $edit_flow; - - $columns = $this->get_columns(); - $hidden = array(); - $sortable = array(); - - $this->_column_headers = array( $columns, $hidden, $sortable ); - - $this->items = $edit_flow->user_groups->get_usergroups(); - - $this->set_pagination_args( array( - 'total_items' => count( $this->items ), - 'per_page' => count( $this->items ), - ) ); - } + class EF_Usergroups_List_Table extends WP_List_Table { - /** - * Message to be displayed when there are no usergroups - * - * @since 0.7 - */ - function no_items() { - _e( 'No user groups found.', 'edit-flow' ); - } - - /** - * Columns in our Usergroups table - * - * @since 0.7 - */ - function get_columns() { - - $columns = array( - 'name' => __( 'Name', 'edit-flow' ), - 'description' => __( 'Description', 'edit-flow' ), - 'users' => __( 'Users in Group', 'edit-flow' ), - ); - - return $columns; - } - - /** - * Process the Usergroup column value for all methods that aren't registered - * - * @since 0.7 - */ - function column_default( $usergroup, $column_name ) { - - } - - /** - * Process the Usergroup name column value. - * Displays the name of the Usergroup, and action links - * - * @since 0.7 - */ - function column_name( $usergroup ) { - global $edit_flow; - - // @todo direct edit link - $output = '' . esc_html( $usergroup->name ) . ''; - - $actions = array(); - $actions['edit edit-usergroup'] = sprintf( '' . __( 'Edit', 'edit-flow' ) . '', $edit_flow->user_groups->get_link( array( 'action' => 'edit-usergroup', 'usergroup-id' => $usergroup->term_id ) ) ); - $actions['inline hide-if-no-js'] = '' . __( 'Quick Edit' ) . ''; - $actions['delete delete-usergroup'] = sprintf( '' . __( 'Delete', 'edit-flow' ) . '', $edit_flow->user_groups->get_link( array( 'action' => 'delete-usergroup', 'usergroup-id' => $usergroup->term_id ) ) ); - - $output .= $this->row_actions( $actions, false ); - $output .= ''; - - return $output; - - } - - /** - * Handle the 'description' column for the table of Usergroups - * Don't need to unencode this because we already did when the usergroup was loaded - * - * @since 0.7 - */ - function column_description( $usergroup ) { - return esc_html( $usergroup->description ); - } - - /** - * Show the "Total Users" in a given usergroup - * - * @since 0.7 - */ - function column_users( $usergroup ) { - global $edit_flow; - return '' . count( $usergroup->user_ids ) . ''; - } - - /** - * Prepare a single row of information about a usergroup - * - * @since 0.7 - */ - function single_row( $usergroup ) { - static $row_class = ''; - $row_class = ( $row_class == '' ? ' class="alternate"' : '' ); + protected $callback_args; - echo ''; - echo $this->single_row_columns( $usergroup ); - echo ''; - } - - /** - * If we use this form, we can have inline editing! - * - * @since 0.7 - */ - function inline_edit() { - global $edit_flow; -?> + public function __construct() { + + parent::__construct( array( + 'plural' => 'user groups', + 'singular' => 'user group', + 'ajax' => true, + ) ); + } + + /** + * @todo Paginate if we have a lot of usergroups + * + * @since 0.7 + */ + public function prepare_items() { + global $edit_flow; + + $columns = $this->get_columns(); + $hidden = array(); + $sortable = array(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + + $this->items = $edit_flow->user_groups->get_usergroups(); + + $this->set_pagination_args( array( + 'total_items' => count( $this->items ), + 'per_page' => count( $this->items ), + ) ); + } + + /** + * Message to be displayed when there are no usergroups + * + * @since 0.7 + */ + public function no_items() { + _e( 'No user groups found.', 'edit-flow' ); + } + + /** + * Columns in our Usergroups table + * + * @since 0.7 + */ + public function get_columns() { + + $columns = array( + 'name' => __( 'Name', 'edit-flow' ), + 'description' => __( 'Description', 'edit-flow' ), + 'users' => __( 'Users in Group', 'edit-flow' ), + ); + + return $columns; + } + + /** + * Process the Usergroup column value for all methods that aren't registered + * + * @since 0.7 + */ + public function column_default( $usergroup, $column_name ) { + } + + /** + * Process the Usergroup name column value. + * Displays the name of the Usergroup, and action links + * + * @since 0.7 + */ + public function column_name( $usergroup ) { + global $edit_flow; + + // @todo direct edit link + $output = '' . esc_html( $usergroup->name ) . ''; + + $actions = array(); + $actions['edit edit-usergroup'] = sprintf( '' . __( 'Edit', 'edit-flow' ) . '', $edit_flow->user_groups->get_link( array( + 'action' => 'edit-usergroup', + 'usergroup-id' => $usergroup->term_id, + ) ) ); + $actions['inline hide-if-no-js'] = '' . __( 'Quick Edit' ) . ''; + $actions['delete delete-usergroup'] = sprintf( '' . __( 'Delete', 'edit-flow' ) . '', $edit_flow->user_groups->get_link( array( + 'action' => 'delete-usergroup', + 'usergroup-id' => $usergroup->term_id, + ) ) ); + + $output .= $this->row_actions( $actions, false ); + $output .= ''; + + return $output; + } + + /** + * Handle the 'description' column for the table of Usergroups + * Don't need to unencode this because we already did when the usergroup was loaded + * + * @since 0.7 + */ + public function column_description( $usergroup ) { + return esc_html( $usergroup->description ); + } + + /** + * Show the "Total Users" in a given usergroup + * + * @since 0.7 + */ + public function column_users( $usergroup ) { + global $edit_flow; + return '' . count( $usergroup->user_ids ) . ''; + } + + /** + * Prepare a single row of information about a usergroup + * + * @since 0.7 + */ + public function single_row( $usergroup ) { + static $row_class = ''; + $row_class = ( '' == $row_class ? ' class="alternate"' : '' ); + + echo wp_kses_post( '' ); + echo wp_kses_post( $this->single_row_columns( $usergroup ) ); + echo ''; + } + + /** + * If we use this form, we can have inline editing! + * + * @since 0.7 + */ + public function inline_edit() { + global $edit_flow; + ?>
-
- Date: Wed, 26 Jun 2024 16:59:24 +1000 Subject: [PATCH 11/15] lint all the other classes as well sabe for the commons folder --- composer.json | 4 +-- edit-flow.php | 2 +- edit_flow.php | 19 +++++------ .../dashboard/widgets/dashboard-notepad.php | 32 ++++++++++--------- phpcs.xml.dist | 7 ++++ vipgo-helper.php | 16 +++++++--- wpcom-helper.php | 18 ++++++++--- 7 files changed, 62 insertions(+), 36 deletions(-) diff --git a/composer.json b/composer.json index 581e2020..8b35f6ff 100644 --- a/composer.json +++ b/composer.json @@ -21,10 +21,10 @@ }, "scripts": { "cs": [ - "@php ./vendor/bin/phpcs -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*\"" + "@php ./vendor/bin/phpcs -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*,/common/*\"" ], "cbf": [ - "@php ./vendor/bin/phpcbf -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*\"" + "@php ./vendor/bin/phpcbf -p -s -v -n . --standard=\"phpcs.xml.dist\" --extensions=php --ignore=\"/vendor/*,/node_modules/*,/tests/*,/common/*\"" ], "integration": "wp-env run tests-cli --env-cwd=wp-content/plugins/Edit-Flow ./vendor/bin/phpunit", "integration-ms": "wp-env run tests-cli --env-cwd=wp-content/plugins/Edit-Flow /bin/bash -c 'WP_MULTISITE=1 ./vendor/bin/phpunit'" diff --git a/edit-flow.php b/edit-flow.php index fdbe94ea..ea5be28e 100644 --- a/edit-flow.php +++ b/edit-flow.php @@ -8,4 +8,4 @@ * * Since this is not the primary plugin file, it does not have the standard WordPress headers. */ -require_once dirname( __FILE__ ) . '/edit_flow.php'; +require_once __DIR__ . '/edit_flow.php'; diff --git a/edit_flow.php b/edit_flow.php index 1f380de1..1584430c 100644 --- a/edit_flow.php +++ b/edit_flow.php @@ -189,9 +189,9 @@ private function setup_actions() { * Inititalizes the Edit Flows! * Loads options for each registered module and then initializes it if it's active */ - function action_init() { + public function action_init() { - load_plugin_textdomain( 'edit-flow', null, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); + load_plugin_textdomain( 'edit-flow', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); $this->load_modules(); @@ -218,7 +218,7 @@ function action_init() { /** * Initialize the plugin for the admin */ - function action_admin_init() { + 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' ); @@ -315,7 +315,7 @@ public function register_module( $name, $args = array() ) { * 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.) */ - function load_module_options() { + public function load_module_options() { foreach ( $this->modules as $mod_name => $mod_data ) { @@ -343,7 +343,7 @@ function load_module_options() { * * @see http://dev.editflow.org/2011/11/17/edit-flow-v0-7-alpha2-notes/#comment-232 */ - function action_init_after() { + public function action_init_after() { foreach ( $this->modules as $mod_name => $mod_data ) { if ( isset( $this->modules->$mod_name->options->post_types ) ) { @@ -360,7 +360,7 @@ function action_init_after() { * @param string $key The property to use for searching a module (ex: 'name') * @param string|int|array $value The value to compare (using ==) */ - function get_module_by( $key, $value ) { + public function get_module_by( $key, $value ) { $module = false; foreach ( $this->modules as $mod_name => $mod_data ) { @@ -380,13 +380,13 @@ function get_module_by( $key, $value ) { /** * Update the $edit_flow object with new value and save to the database */ - function update_module_option( $mod_name, $key, $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 ); } - function update_all_module_options( $mod_name, $new_options ) { + public function update_all_module_options( $mod_name, $new_options ) { if ( is_array( $new_options ) ) { $new_options = (object) $new_options; } @@ -398,7 +398,7 @@ function update_all_module_options( $mod_name, $new_options ) { /** * Registers commonly used scripts + styles for easy enqueueing */ - function register_scripts_and_styles() { + public function register_scripts_and_styles() { wp_enqueue_style( 'ef-admin-css', EDIT_FLOW_URL . 'common/css/edit-flow-admin.css', false, EDIT_FLOW_VERSION, 'all' ); wp_register_script( 'jquery-listfilterizer', EDIT_FLOW_URL . 'common/js/jquery.listfilterizer.js', array( 'jquery' ), EDIT_FLOW_VERSION, true ); @@ -418,6 +418,7 @@ function register_scripts_and_styles() { } } +// phpcs:disable WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid function EditFlow() { return edit_flow::instance(); } diff --git a/modules/dashboard/widgets/dashboard-notepad.php b/modules/dashboard/widgets/dashboard-notepad.php index fa789b60..71662b2f 100644 --- a/modules/dashboard/widgets/dashboard-notepad.php +++ b/modules/dashboard/widgets/dashboard-notepad.php @@ -5,21 +5,21 @@ class EF_Dashboard_Notepad_Widget { + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase const notepad_post_type = 'dashboard-note'; public $edit_cap = 'edit_others_posts'; - function __construct() { + public function __construct() { // Silence is golden } public function init() { register_post_type( self::notepad_post_type, array( - 'rewrite' => false, - 'label' => __( 'Dashboard Note', 'edit-flow' ) - ) - ); + 'rewrite' => false, + 'label' => __( 'Dashboard Note', 'edit-flow' ), + )); $this->edit_cap = apply_filters( 'ef_dashboard_notepad_edit_cap', $this->edit_cap ); @@ -41,7 +41,7 @@ public function handle_notepad_update() { check_admin_referer( 'dashboard-notepad' ); if ( ! current_user_can( $this->edit_cap ) ) { - wp_die( EditFlow()->dashboard->messages['invalid-permissions'] ); + wp_die( esc_html( EditFlow()->dashboard->messages['invalid-permissions'] ) ); } $note_data = array( @@ -68,19 +68,22 @@ public function handle_notepad_update() { public function notepad_widget() { $args = array( - 'posts_per_page' => 1, - 'post_status' => 'draft', - 'post_type' => self::notepad_post_type, - ); + 'posts_per_page' => 1, + 'post_status' => 'draft', + 'post_type' => self::notepad_post_type, + ); + $posts = get_posts( $args ); $current_note = ( ! empty( $posts[0]->post_content ) ) ? $posts[0]->post_content : ''; $current_id = ( ! empty( $posts[0]->ID ) ) ? $posts[0]->ID : 0; $current_post = ( ! empty( $posts[0] ) ) ? $posts[0] : false; - if ( $current_post ) + if ( $current_post ) { + // translators: %1$s is the author name, %2$s is the date the note was last updated $last_updated = '' . sprintf( __( '%1$s last updated on %2$s', 'edit-flow' ), get_user_by( 'id', $current_post->post_author )->display_name, get_the_modified_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $current_post ) ) . ''; - else + } else { $last_updated = ''; + } if ( current_user_can( $this->edit_cap ) ) { echo '
'; @@ -90,7 +93,7 @@ public function notepad_widget() { echo esc_textarea( trim( $current_note ) ); echo ''; echo '

'; - echo $last_updated; + echo wp_kses_post( $last_updated ); echo ''; submit_button( __( 'Update Note', 'edit-flow' ), 'primary', 'update-note', false ); echo ''; @@ -102,9 +105,8 @@ public function notepad_widget() { echo ''; - echo $last_updated; + echo wp_kses_post( $last_updated ); echo '

'; } } - } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 318e2134..5a06b054 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -17,6 +17,13 @@ + + + + + + + diff --git a/vipgo-helper.php b/vipgo-helper.php index be016647..5603fe95 100644 --- a/vipgo-helper.php +++ b/vipgo-helper.php @@ -9,10 +9,18 @@ * them via filters. */ add_filter( 'ef_kill_add_caps_to_role', '__return_true' ); -add_filter( 'ef_view_calendar_cap', function() {return 'edit_posts'; } ); -add_filter( 'ef_view_story_budget_cap', function() { return 'edit_posts'; } ); -add_filter( 'ef_edit_post_subscriptions_cap', function() { return 'edit_others_posts'; } ); -add_filter( 'ef_manage_usergroups_cap', function() { return 'manage_options'; } ); +add_filter( 'ef_view_calendar_cap', function () { + return 'edit_posts'; +} ); +add_filter( 'ef_view_story_budget_cap', function () { + return 'edit_posts'; +} ); +add_filter( 'ef_edit_post_subscriptions_cap', function () { + return 'edit_others_posts'; +} ); +add_filter( 'ef_manage_usergroups_cap', function () { + return 'manage_options'; +} ); /** * Edit Flow loads modules after plugins_loaded, which has already been fired when loading via wpcom_vip_load_plugins diff --git a/wpcom-helper.php b/wpcom-helper.php index 519b5eb9..7bd595d2 100644 --- a/wpcom-helper.php +++ b/wpcom-helper.php @@ -9,10 +9,18 @@ * them with the WP.com + core caps approach */ add_filter( 'ef_kill_add_caps_to_role', '__return_true' ); -add_filter( 'ef_view_calendar_cap', function() { return 'edit_posts'; } ); -add_filter( 'ef_view_story_budget_cap', function() { return 'edit_posts'; } ); -add_filter( 'ef_edit_post_subscriptions_cap', function() { return 'edit_others_posts'; } ); -add_filter( 'ef_manage_usergroups_cap', function() { return 'manage_options'; } ); +add_filter( 'ef_view_calendar_cap', function () { + return 'edit_posts'; +} ); +add_filter( 'ef_view_story_budget_cap', function () { + return 'edit_posts'; +} ); +add_filter( 'ef_edit_post_subscriptions_cap', function () { + return 'edit_others_posts'; +} ); +add_filter( 'ef_manage_usergroups_cap', function () { + return 'manage_options'; +} ); /** * Edit Flow loads modules after plugins_loaded, which has already been fired on WP.com @@ -52,4 +60,4 @@ function edit_flow_fix_fix_post_name( $post ) { } return $post; -} \ No newline at end of file +} From a365a120616ce779b6c0d06f2cfe132e19a4a6b6 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 26 Jun 2024 20:03:19 +1000 Subject: [PATCH 12/15] Add comments to the new rules in the phpcs file --- phpcs.xml.dist | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 5a06b054..9166b05b 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -14,14 +14,16 @@ - - - + + + + + @@ -29,11 +31,13 @@ + + From 0c6d169d72a8d91145d034d987371c97447b6869 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Wed, 26 Jun 2024 20:31:44 +1000 Subject: [PATCH 13/15] Group the exceptions under a todo --- phpcs.xml.dist | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 9166b05b..b273e571 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -14,29 +14,28 @@ - - - - - - - - - + + + + + + + + + - From edae485222e251a3da95a3ddb597f91c29d54134 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 27 Jun 2024 10:23:35 +1000 Subject: [PATCH 14/15] Revert the switching of date to gmdate --- modules/calendar/calendar.php | 54 +++++++++---------- .../editorial-metadata/editorial-metadata.php | 8 +-- modules/notifications/notifications.php | 2 +- modules/story-budget/story-budget.php | 6 +-- phpcs.xml.dist | 1 + 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/modules/calendar/calendar.php b/modules/calendar/calendar.php index 239fef36..6c41288d 100644 --- a/modules/calendar/calendar.php +++ b/modules/calendar/calendar.php @@ -357,10 +357,10 @@ public function handle_ajax_drag_and_drop() { // Persist the old hourstamp because we can't manipulate the exact time on the calendar // Bump the last modified timestamps too - $existing_time = gmdate( 'H:i:s', strtotime( $post->post_date ) ); - $existing_time_gmt = gmdate( 'H:i:s', strtotime( $post->post_date_gmt ) ); + $existing_time = date( 'H:i:s', strtotime( $post->post_date ) ); + $existing_time_gmt = date( 'H:i:s', strtotime( $post->post_date_gmt ) ); $new_values = array( - 'post_date' => gmdate( 'Y-m-d', $next_date_full ) . ' ' . $existing_time, + 'post_date' => date( 'Y-m-d', $next_date_full ) . ' ' . $existing_time, 'post_modified' => current_time( 'mysql' ), 'post_modified_gmt' => current_time( 'mysql', 1 ), ); @@ -369,7 +369,7 @@ public function handle_ajax_drag_and_drop() { // If the user desires that to be the behaviour, they can set the result of this filter to 'true' // With how WordPress works internally, setting 'post_date_gmt' will set the timestamp if ( apply_filters( 'ef_calendar_allow_ajax_to_set_timestamp', false ) ) { - $new_values['post_date_gmt'] = gmdate( 'Y-m-d', $next_date_full ) . ' ' . $existing_time_gmt; + $new_values['post_date_gmt'] = date( 'Y-m-d', $next_date_full ) . ' ' . $existing_time_gmt; } // We have to do SQL unfortunately because of core bugginess @@ -421,7 +421,7 @@ public function handle_ics_subscription() { } // Set the start date for the posts_where filter - $this->start_date = apply_filters( 'ef_calendar_ics_subscription_start_date', $this->get_beginning_of_week( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ) ); + $this->start_date = apply_filters( 'ef_calendar_ics_subscription_start_date', $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) ) ); $this->total_weeks = apply_filters( 'ef_calendar_total_weeks', $this->total_weeks, 'ics_subscription' ); @@ -566,7 +566,7 @@ public static function ics_format_time( $time_string, $offset_in_seconds = 0 ) { $timestamp += $offset_in_seconds; // \T and \Z are escaped for literal T and Z characters - return gmdate( 'Ymd\THis\Z', $timestamp ); + return date( 'Ymd\THis\Z', $timestamp ); } /** @@ -640,7 +640,7 @@ public function get_filters() { 'cat' => '', 'author' => '', 'num_weeks' => $this->total_weeks, - 'start_date' => gmdate( 'Y-m-d', current_time( 'timestamp' ) ), + 'start_date' => date( 'Y-m-d', current_time( 'timestamp' ) ), ); $old_filters = array_merge( $default_filters, isset( $screen_options['num_weeks'] ) ? array( 'num_weeks' => $screen_options['num_weeks'] ) : array(), (array) $old_filters ); @@ -699,7 +699,7 @@ public function view_calendar() { $heading_date = $filters['start_date']; for ( $i = 0; $i < 7; $i++ ) { $dates[ $i ] = $heading_date; - $heading_date = gmdate( 'Y-m-d', strtotime( '+1 day', strtotime( $heading_date ) ) ); + $heading_date = date( 'Y-m-d', strtotime( '+1 day', strtotime( $heading_date ) ) ); } // we sort by post statuses....... eventually @@ -776,7 +776,7 @@ public function view_calendar() { $split_month = $single_date_month; $current_month = $single_date_month; } - $week_single_date = gmdate( 'Y-m-d', strtotime( '+1 day', strtotime( $week_single_date ) ) ); + $week_single_date = date( 'Y-m-d', strtotime( '+1 day', strtotime( $week_single_date ) ) ); } ?> @@ -823,13 +823,13 @@ public function view_calendar() { $td_classes = array( 'day-unit', ); - $day_name = gmdate( 'D', strtotime( $week_single_date ) ); + $day_name = date( 'D', strtotime( $week_single_date ) ); if ( in_array( $day_name, $dotw ) ) { $td_classes[] = 'weekend-day'; } - if ( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) == $week_single_date ) { + if ( date( 'Y-m-d', current_time( 'timestamp' ) ) == $week_single_date ) { $td_classes[] = 'today'; } @@ -842,10 +842,10 @@ public function view_calendar() { ?> - +
-
+
    hidden = 0; @@ -869,7 +869,7 @@ public function view_calendar() { create_post_cap ) ) : - $date_formatted = gmdate( 'D, M jS, Y', strtotime( $week_single_date ) ); + $date_formatted = date( 'D, M jS, Y', strtotime( $week_single_date ) ); ?>
    @@ -1272,7 +1272,7 @@ public function get_calendar_posts_for_week( $args = array(), $context = 'dashbo } $beginning_date = $this->get_beginning_of_week( $this->start_date, 'Y-m-d', $this->current_week ); - $ending_date = gmdate( 'Y-m-d', strtotime( $beginning_date ) + WEEK_IN_SECONDS ); + $ending_date = date( 'Y-m-d', strtotime( $beginning_date ) + WEEK_IN_SECONDS ); $args['date_query'] = array( 'after' => $beginning_date, @@ -1288,7 +1288,7 @@ public function get_calendar_posts_for_week( $args = array(), $context = 'dashbo while ( $post_results->have_posts() ) { $post_results->the_post(); global $post; - $key_date = gmdate( 'Y-m-d', strtotime( $post->post_date ) ); + $key_date = date( 'Y-m-d', strtotime( $post->post_date ) ); $posts[ $key_date ][] = $post; } @@ -1310,14 +1310,14 @@ public function get_pagination_link( $direction = 'next', $filters = array(), $w if ( ! isset( $weeks_offset ) ) { $weeks_offset = $this->total_weeks; } else if ( 0 == $weeks_offset ) { - $filters['start_date'] = $this->get_beginning_of_week( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ); + $filters['start_date'] = $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) ); } if ( 'previous' == $direction ) { $weeks_offset = '-' . $weeks_offset; } - $filters['start_date'] = gmdate( 'Y-m-d', strtotime( $weeks_offset . ' weeks', strtotime( $filters['start_date'] ) ) ); + $filters['start_date'] = date( 'Y-m-d', strtotime( $weeks_offset . ' weeks', strtotime( $filters['start_date'] ) ) ); $url = add_query_arg( $filters, menu_page_url( $this->module->slug, false ) ); if ( count( $supported_post_types ) > 1 ) { @@ -1342,10 +1342,10 @@ public function get_beginning_of_week( $date, $format = 'Y-m-d', $week = 1 ) { $date = strtotime( $date ); $start_of_week = get_option( 'start_of_week' ); - $day_of_week = gmdate( 'w', $date ); + $day_of_week = date( 'w', $date ); $date += ( ( $start_of_week - $day_of_week - 7 ) % 7 ) * 60 * 60 * 24; $date = strtotime( '+' . ( $week - 1 ) . ' week', $date ); - $formatted_start_of_week = gmdate( $format, $date ); + $formatted_start_of_week = date( $format, $date ); return $formatted_start_of_week; } @@ -1364,10 +1364,10 @@ public function get_ending_of_week( $date, $format = 'Y-m-d', $week = 1 ) { $date = strtotime( $date ); $end_of_week = get_option( 'start_of_week' ) - 1; - $day_of_week = gmdate( 'w', $date ); + $day_of_week = date( 'w', $date ); $date += ( ( $end_of_week - $day_of_week + 7 ) % 7 ) * 60 * 60 * 24; $date = strtotime( '+' . ( $week - 1 ) . ' week', $date ); - $formatted_end_of_week = gmdate( $format, $date ); + $formatted_end_of_week = date( $format, $date ); return $formatted_end_of_week; } @@ -1382,7 +1382,7 @@ public function calendar_time_range() { $first_datetime = strtotime( $this->start_date ); $first_date = date_i18n( get_option( 'date_format' ), $first_datetime ); $total_days = ( $this->total_weeks * 7 ) - 1; - $last_datetime = strtotime( '+' . $total_days . ' days', gmdate( 'U', strtotime( $this->start_date ) ) ); + $last_datetime = strtotime( '+' . $total_days . ' days', date( 'U', strtotime( $this->start_date ) ) ); $last_date = date_i18n( get_option( 'date_format' ), $last_datetime ); // translators: %1$s = first date, %2$s = last date echo esc_html( sprintf( __( 'for %1$s through %2$s', 'edit-flow' ), $first_date, $last_date ) ); @@ -1572,7 +1572,7 @@ public function handle_ajax_insert_post() { $post_placeholder = array( 'post_title' => $post_title, 'post_status' => $post_status, - 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( $post_date ) ), + 'post_date' => date( 'Y-m-d H:i:s', strtotime( $post_date ) ), 'post_type' => $this->module->options->quick_create_post_type, ); @@ -1580,7 +1580,7 @@ public function handle_ajax_insert_post() { // If the user desires that to be the behavior, they can set the result of this filter to 'true' // With how WordPress works internally, setting 'post_date_gmt' will set the timestamp if ( apply_filters( 'ef_calendar_allow_ajax_to_set_timestamp', false ) ) { - $post_placeholder['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', strtotime( $post_date ) ); + $post_placeholder['post_date_gmt'] = date( 'Y-m-d H:i:s', strtotime( $post_date ) ); } // Create the post @@ -1752,7 +1752,7 @@ public function sanitize_filter( $key, $dirty_value ) { } break; case 'start_date': - return gmdate( 'Y-m-d', strtotime( $dirty_value ) ); + return date( 'Y-m-d', strtotime( $dirty_value ) ); break; case 'cat': case 'author': @@ -1906,7 +1906,7 @@ function ( $item ) { 'MAX' => $this->max_weeks, 'DEFAULT' => $this->total_weeks, ), - 'BEGINNING_OF_WEEK' => $this->get_beginning_of_week( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ), + 'BEGINNING_OF_WEEK' => $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) ), 'FILTERS' => $this->get_filters(), 'PAGE_URL' => menu_page_url( $this->module->slug, false ), 'WP_VERSION' => $wp_version, diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index 1dd6ccb6..7fdffc61 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -460,7 +460,7 @@ public function display_meta_box( $post ) { */ private function show_date_or_datetime( $current_date ) { - if ( gmdate( 'Hi', $current_date ) == '0000' ) { + if ( date( 'Hi', $current_date ) == '0000' ) { return date_i18n( 'M d Y', $current_date ); } else { return date_i18n( 'M d Y H:i', $current_date ); @@ -863,9 +863,9 @@ private function generate_editorial_metadata_term_output( $term, $pm_value ) { } // All day vs. day and time - $date = gmdate( get_option( 'date_format' ), $pm_value ); - $time = gmdate( get_option( 'time_format' ), $pm_value ); - if ( '0000' == gmdate( 'Hi', $pm_value ) ) { + $date = date( get_option( 'date_format' ), $pm_value ); + $time = date( get_option( 'time_format' ), $pm_value ); + if ( '0000' == date( 'Hi', $pm_value ) ) { $pm_value = $date; } else { // translators: 1: date, 2: time diff --git a/modules/notifications/notifications.php b/modules/notifications/notifications.php index ff8cc13b..45331e8c 100644 --- a/modules/notifications/notifications.php +++ b/modules/notifications/notifications.php @@ -836,7 +836,7 @@ public function get_notification_footer( $post ) { $body .= sprintf( __( 'You are receiving this email because you are subscribed to "%s".', 'edit-flow' ), ef_draft_or_post_title( $post->ID ) ); $body .= "\r\n"; /* translators: 1: date */ - $body .= sprintf( __( 'This email was sent %s.', 'edit-flow' ), gmdate( 'r' ) ); + $body .= sprintf( __( 'This email was sent %s.', 'edit-flow' ), date( 'r' ) ); $body .= "\r\n \r\n"; $body .= get_option( 'blogname' ) . ' | ' . get_bloginfo( 'url' ) . ' | ' . admin_url( '/' ) . "\r\n"; return $body; diff --git a/modules/story-budget/story-budget.php b/modules/story-budget/story-budget.php index 43285cba..3462b6ef 100644 --- a/modules/story-budget/story-budget.php +++ b/modules/story-budget/story-budget.php @@ -433,8 +433,8 @@ public function get_posts_for_term( $term, $args = null ) { $ending_date = $beginning_date + ( $days_to_show * DAY_IN_SECONDS ); $args['date_query'] = array( - 'after' => gmdate( 'Y-m-d', $beginning_date ), - 'before' => gmdate( 'Y-m-d', $ending_date ), + 'after' => date( 'Y-m-d', $beginning_date ), + 'before' => date( 'Y-m-d', $ending_date ), 'inclusive' => true, ); @@ -717,7 +717,7 @@ public function update_user_filters() { } if ( ! $user_filters['start_date'] ) { - $user_filters['start_date'] = gmdate( 'Y-m-d' ); + $user_filters['start_date'] = date( 'Y-m-d' ); } if ( ! $user_filters['number_days'] ) { diff --git a/phpcs.xml.dist b/phpcs.xml.dist index b273e571..5f1e8589 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -38,6 +38,7 @@ + . From b8e68c111a596aca14999606f17fdde39388b454 Mon Sep 17 00:00:00 2001 From: ingeniumed Date: Thu, 27 Jun 2024 14:47:11 +1000 Subject: [PATCH 15/15] Address the feedback related to linting --- modules/calendar/calendar.php | 7 +-- modules/custom-status/custom-status.php | 37 +++++-------- modules/dashboard/dashboard.php | 6 +-- .../editorial-comments/editorial-comments.php | 10 ++-- .../editorial-metadata/editorial-metadata.php | 52 ++++++++----------- modules/story-budget/story-budget.php | 2 +- modules/user-groups/user-groups.php | 31 ++++------- 7 files changed, 55 insertions(+), 90 deletions(-) diff --git a/modules/calendar/calendar.php b/modules/calendar/calendar.php index 6c41288d..44a84d35 100644 --- a/modules/calendar/calendar.php +++ b/modules/calendar/calendar.php @@ -1005,11 +1005,8 @@ public function get_inner_information( $ef_calendar_item_information_fields, $po : current_user_can_modify_post( $post ) ) : ?> - + + get_editable_html( $values['type'], $values['value'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 83dce774..f8ec5739 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -844,7 +844,8 @@ public function handle_add_custom_status() { ); $return = $this->add_custom_status( $status_name, $status_args ); if ( is_wp_error( $return ) ) { - wp_die( esc_textarea( __( 'Could not add status: ', 'edit-flow' ) . $return->get_error_message() ) ); + /* translators: %s: error message */ + wp_die( esc_html( sprintf( __( 'Could not add status: %s', 'edit-flow' ), $return->get_error_message() ) ) ); } // Redirect if successful $redirect_url = $this->get_link( array( 'message' => 'status-added' ) ); @@ -929,7 +930,7 @@ public function handle_edit_custom_status() { ); $return = $this->update_custom_status( $existing_status->term_id, $args ); if ( is_wp_error( $return ) ) { - wp_die( esc_textarea( __( 'Error updating post status.', 'edit-flow' ) ) ); + wp_die( esc_html__( 'Error updating post status.', 'edit-flow' ) ); } $redirect_url = $this->get_link( array( 'message' => 'status-updated' ) ); @@ -1151,8 +1152,8 @@ public function ajax_inline_save_status() { die(); } else { /* translators: 1: the status's name */ - $change_error = new WP_Error( 'invalid', wp_kses( sprintf( __( 'Could not update the status: %s', 'edit-flow' ), $status_name ), 'strong' ) ); - die( esc_html( $change_error->get_error_message() ) ); + $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the status: %s', 'edit-flow' ), $status_name ) ); + die( wp_kses( $change_error->get_error_message(), 'strong' ) ); } } @@ -1260,12 +1261,8 @@ public function print_configure_view() { - @@ -1309,18 +1306,10 @@ public function print_configure_view() {
    @@ -1334,12 +1323,12 @@ public function print_configure_view() {
    - + settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the status. (Max: 20 characters)', 'edit-flow' ) ); ?>
    - + settings->helper_print_error_or_description( 'description', __( 'The description is primarily for administrative use, to give you some context on what the custom status is to be used for.', 'edit-flow' ) ); ?>
    diff --git a/modules/dashboard/dashboard.php b/modules/dashboard/dashboard.php index 6a8c1d39..957e68da 100644 --- a/modules/dashboard/dashboard.php +++ b/modules/dashboard/dashboard.php @@ -239,12 +239,12 @@ public function myposts_widget() { $title = esc_html( $post->post_title ); ?>
  • -

    - +

    +
  • -

    +

    - + @@ -228,8 +228,7 @@ public function maybe_output_comment_meta( $comment_id ) { $message = '' . esc_html__( 'Notified', 'edit-flow' ) . ': ' . esc_html( $notification ); } - // It's already been escaped above. - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- It's already been escaped above echo '

    ' . $message . '

    '; } @@ -242,9 +241,8 @@ public function the_comment( $comment, $args, $depth ) { // Get current user wp_get_current_user(); - // Without this, the comment will not appear. // ToDo: Find an alternative so we don't override global variables - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Without this, the comment will not appear. $GLOBALS['comment'] = $comment; $actions = array(); diff --git a/modules/editorial-metadata/editorial-metadata.php b/modules/editorial-metadata/editorial-metadata.php index 7fdffc61..d75a15ae 100644 --- a/modules/editorial-metadata/editorial-metadata.php +++ b/modules/editorial-metadata/editorial-metadata.php @@ -293,7 +293,8 @@ public function add_admin_scripts() { $css_rules = apply_filters( 'ef_editorial_metadata_manage_posts_css_rules', $css_rules ); echo "'; } @@ -357,7 +358,7 @@ public function handle_post_metaboxes() { public function display_meta_box( $post ) { echo "
    "; // Add nonce for verification upon save - echo ""; + echo ""; if ( current_user_can( 'manage_options' ) ) { // Make the metabox title include a link to edit the Editorial Metadata terms. Logic similar to how Core dashboard widgets work. @@ -386,8 +387,7 @@ public function display_meta_box( $post ) { } else { $description_span = ''; } - // This is for the escaping of type. - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is for the escaping of type. echo "
    slug ) { - echo 'readonly="readonly"';} - ?> - /> + slug ? 'readonly="readonly"' : ''; ?> + /> settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the status. (Max: 20 characters)', 'edit-flow' ) ); ?>