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' => __( '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' => __( '
-
-
- taxonomy = EF_Editorial_Metadata::metadata_taxonomy;
-
+
$this->tax = get_taxonomy( $this->taxonomy );
-
- $columns = $this->get_columns();
- $hidden = array(
+
+ $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',
+ 'plural' => 'editorial metadata',
'singular' => 'editorial metadata',
) );
}
@@ -1608,13 +1849,13 @@ 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();
$this->set_pagination_args( array(
'total_items' => count( $this->items ),
- 'per_page' => count( $this->items ),
+ 'per_page' => count( $this->items ),
) );
}
@@ -1623,117 +1864,129 @@ 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
+ * Prepare a single row of Editorial Metadata.
*
* @since 0.7
*
- * @param object $term The current term we're displaying
- * @param int $level Level is always zero because it isn't a parent-child tax
+ * @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' : '' );
- $row_class = ' class="term-static' . $alternate_class . '"';
+ $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
+ * Handle the column output when there's no method for it.
*
* @since 0.7
*
- * @param object $item Editorial Metadata term as an object
- * @param string $column_name How the column was registered at birth
+ * @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;
}
-
}
/**
- * Column for displaying the term's name and associated actions
+ * Column for displaying the term's name and associated actions.
*
* @since 0.7
*
- * @param object $item Editorial Metadata term as an object
+ * @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['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' ) . "";
-
+
+ $actions = array();
+ $actions['edit'] = "
" . __( 'Edit', 'edit-flow' ) . '';
+ $actions['inline hide-if-no-js'] = '
' . __( 'Quick Edit', '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 .= '
';
$out .= '
' . $item->name . '
';
- $out .= '
' . $item->description . '
';
+ $out .= '
' . $item->description . '
';
$out .= '
';
-
+
return $out;
}
/**
- * Admins can use the inline edit capability to quickly make changes to the title or description
+ * Admins can use the inline edit capability to quickly make changes to the title or description.
*
* @since 0.7
*/
- function inline_edit() {
+ public function inline_edit() {
-?>
+ ?>
- ' ).addClass( 'post_following_list-no_access' );
- span.text( ef_notifications_localization.no_access );
- $( container ).parent().prepend( span );
+ const span = $( '
' ).addClass( 'post_following_list-no_access' );
+ span.text( localization.no_access );
+ $actionsDiv.prepend( span );
warning_background = true;
}
- // "No Email" If this user was flagged as not having an email
- const user_has_no_email = response.data.subscribers_with_no_email.includes(
- parseInt( $( container ).val() )
- );
+
+ // "No Email" If this user was flagged as not having an email.
+ const user_has_no_email = response.data.subscribers_with_no_email.includes( userId );
if ( user_has_no_email ) {
- var span = $( '
' ).addClass( 'post_following_list-no_email' );
- span.text( ef_notifications_localization.no_email );
- $( container ).parent().prepend( span );
+ const span = $( '
' ).addClass( 'post_following_list-no_email' );
+ span.text( localization.no_email );
+ $actionsDiv.prepend( span );
warning_background = true;
}
};
@@ -87,4 +108,18 @@ jQuery( document ).ready( function ( $ ) {
} );
}
);
+
+ // TODO: Should change this to _not_ use JQuery
+ const webhookUrl = $( 'input#webhook_url' ).closest( 'tr' );
+ const sendToWebhook = $( 'select#send_to_webhook' );
+ if ( sendToWebhook.val() === 'off' ) {
+ webhookUrl.hide();
+ }
+ sendToWebhook.on( 'change', function () {
+ if ( $( this ).val() === 'off' ) {
+ webhookUrl.hide();
+ } else {
+ webhookUrl.show();
+ }
+ } );
} );
diff --git a/modules/notifications/notifications.php b/modules/notifications/notifications.php
index 9b05baa74..088619bb8 100644
--- a/modules/notifications/notifications.php
+++ b/modules/notifications/notifications.php
@@ -1,1295 +1,1570 @@
module_url = $this->get_module_url( __FILE__ );
- $args = array(
- 'title' => __( 'Notifications', 'edit-flow' ),
- 'short_description' => __( 'Update your team of important changes to your content.', 'edit-flow' ),
- 'extended_description' => __( 'With email notifications, you can keep everyone updated about what’s happening with a given content. Each status change or editorial comment sends out an email notification to users subscribed to a post. User groups can be used to manage who receives notifications on what.', 'edit-flow' ),
- 'module_url' => $this->module_url,
- 'img_url' => $this->module_url . 'lib/notifications_s128.png',
- 'slug' => 'notifications',
- 'default_options' => array(
- 'enabled' => 'on',
- 'post_types' => array(
- 'post' => 'on',
- 'page' => 'on',
- ),
- 'always_notify_admin' => 'off',
- ),
- 'configure_page_cb' => 'print_configure_view',
- 'post_type_support' => 'ef_notification',
- 'autoload' => false,
- 'settings_help_tab' => array(
- 'id' => 'ef-notifications-overview',
- 'title' => __('Overview', 'edit-flow'),
- 'content' => __('
Notifications ensure you keep up to date with progress your most important content. Users can be subscribed to notifications on a post one by one or by selecting user groups.
When enabled, email notifications can be sent when a post changes status or an editorial comment is left by a writer or an editor.
', 'edit-flow'),
- ),
- 'settings_help_sidebar' => __( '
For more information:
Notifications Documentation
Edit Flow Forum
Edit Flow on Github
', 'edit-flow' ),
- );
- $this->module = EditFlow()->register_module( 'notifications', $args );
-
- }
-
/**
- * Initialize the notifications class if the plugin is enabled
+ * Notifications module for Edit Flow.
*/
- function init() {
-
- // Register our taxonomies for managing relationships
- $this->register_taxonomies();
-
- // Allow users to use a different user capability for editing post subscriptions
- $this->edit_post_subscriptions_cap = apply_filters( 'ef_edit_post_subscriptions_cap', $this->edit_post_subscriptions_cap );
-
- // Set up metabox and related actions
- add_action( 'add_meta_boxes', array( $this, 'add_post_meta_box' ) );
-
- // Add "access badge" to the subscribers list.
- add_action( 'ef_user_subscribe_actions', array( $this, 'display_subscriber_warning_badges' ), 10, 2 );
-
- // Saving post actions
- // self::save_post_subscriptions() is hooked into transition_post_status so we can ensure usergroup data
- // is properly saved before sending notifs
- add_action( 'transition_post_status', array( $this, 'save_post_subscriptions' ), 0, 3 );
- add_action( 'transition_post_status', array( $this, 'notification_status_change' ), 10, 3 );
- add_action( 'ef_post_insert_editorial_comment', array( $this, 'notification_comment') );
- add_action( 'delete_user', array($this, 'delete_user_action') );
- add_action( 'ef_send_scheduled_email', array( $this, 'send_single_email' ), 10, 4 );
-
- add_action( 'admin_init', array( $this, 'register_settings' ) );
-
- // 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' ) );
-
- // Add a "Follow" link to posts
- if ( apply_filters( 'ef_notifications_show_follow_link', true ) ) {
- // A little extra JS for the follow button
- add_action( 'admin_head', array( $this, 'action_admin_head_follow_js' ) );
- // Manage Posts
- add_filter( 'post_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
- add_filter( 'page_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
- // Calendar and Story Budget
- add_filter( 'ef_calendar_item_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
- add_filter( 'ef_story_budget_item_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
+ class EF_Notifications extends EF_Module {
+
+ /**
+ * Taxonomy name used to store users following posts.
+ *
+ * @var string
+ */
+ public $following_users_taxonomy = 'following_users';
+
+ /**
+ * Taxonomy name used to store user groups following posts.
+ *
+ * @var string
+ */
+ public $following_usergroups_taxonomy = EF_User_Groups::taxonomy_key;
+
+ /**
+ * The module instance.
+ *
+ * @var object
+ */
+ public $module;
+
+ /**
+ * Capability required to edit post subscriptions.
+ *
+ * @var string
+ */
+ public $edit_post_subscriptions_cap = 'edit_post_subscriptions';
+
+ /**
+ * Register the module with Edit Flow but don't do anything else.
+ */
+ public function __construct() {
+
+ // Register the module with Edit Flow.
+ $this->module_url = $this->get_module_url( __FILE__ );
+ $args = [
+ 'title' => __( 'Notifications', 'edit-flow' ),
+ 'short_description' => __( 'Update your team of important changes to your content.', 'edit-flow' ),
+ 'extended_description' => __( 'With email notifications, you can keep everyone updated about what’s happening with a given content. Each status change or editorial comment sends out an email notification to users subscribed to a post. User groups can be used to manage who receives notifications on what. With webhook notifications, all notifications will also be sent to the specified webhook URL(i.e.: Slack incoming webhooks) but will ignore specific user or user groups subscription settings.', 'edit-flow' ),
+ 'module_url' => $this->module_url,
+ 'img_url' => $this->module_url . 'lib/notifications_s128.png',
+ 'slug' => 'notifications',
+ 'default_options' => [
+ 'enabled' => 'on',
+ 'post_types' => [
+ 'post' => 'on',
+ 'page' => 'on',
+ ],
+ 'always_notify_admin' => 'off',
+ 'send_to_webhook' => 'off',
+ 'webhook_url' => '',
+ ],
+ 'configure_page_cb' => 'print_configure_view',
+ 'post_type_support' => 'ef_notification',
+ 'autoload' => false,
+ 'settings_help_tab' => [
+ 'id' => 'ef-notifications-overview',
+ 'title' => __( 'Overview', 'edit-flow' ),
+ 'content' => __( '
Notifications ensure you keep up to date with progress your most important content. Users can be subscribed to notifications on a post one by one or by selecting user groups.
When enabled, email notifications can be sent when a post changes status or an editorial comment is left by a writer or an editor.
', 'edit-flow' ),
+ ],
+ 'settings_help_sidebar' => __( '
For more information:
Notifications Documentation
Edit Flow Forum
Edit Flow on Github
', 'edit-flow' ),
+ ];
+ $this->module = EditFlow()->register_module( 'notifications', $args );
}
- //Ajax for saving notifiction updates
- add_action( 'wp_ajax_save_notifications', array( $this, 'ajax_save_post_subscriptions' ) );
- add_action( 'wp_ajax_ef_notifications_user_post_subscription', array( $this, 'handle_user_post_subscription' ) );
-
- }
-
- /**
- * Load the capabilities onto users the first time the module is run
- *
- * @since 0.7
- */
- function install() {
+ /**
+ * Initialize the notifications class if the plugin is enabled.
+ */
+ public function init() {
+
+ // Register our taxonomies for managing relationships.
+ $this->register_taxonomies();
+
+ // Allow users to use a different user capability for editing post subscriptions.
+ $this->edit_post_subscriptions_cap = apply_filters( 'ef_edit_post_subscriptions_cap', $this->edit_post_subscriptions_cap );
+
+ // Set up metabox and related actions.
+ add_action( 'add_meta_boxes', [ $this, 'add_post_meta_box' ] );
+
+ // Add "access badge" to the subscribers list.
+ add_action( 'ef_user_subscribe_actions', [ $this, 'display_subscriber_warning_badges' ], 10, 2 );
+
+ // Saving post actions.
+ // self::save_post_subscriptions() is hooked into transition_post_status so we can ensure usergroup data
+ // is properly saved before sending notifs.
+ add_action( 'transition_post_status', [ $this, 'save_post_subscriptions' ], 0, 3 );
+ add_action( 'transition_post_status', [ $this, 'notification_status_change' ], 10, 3 );
+ add_action( 'ef_post_insert_editorial_comment', [ $this, 'notification_comment' ] );
+ add_action( 'delete_user', [ $this, 'delete_user_action' ] );
+ add_action( 'ef_send_scheduled_email', [ $this, 'send_single_email' ], 10, 4 );
+
+ add_action( 'admin_init', [ $this, 'register_settings' ] );
+
+ // Javascript and CSS if we need it.
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_styles' ] );
+
+ // Add a "Follow" link to posts.
+ if ( apply_filters( 'ef_notifications_show_follow_link', true ) ) {
+ // A little extra JS for the follow button.
+ add_action( 'admin_head', [ $this, 'action_admin_head_follow_js' ] );
+ // Manage Posts.
+ add_filter( 'post_row_actions', [ $this, 'filter_post_row_actions' ], 10, 2 );
+ add_filter( 'page_row_actions', [ $this, 'filter_post_row_actions' ], 10, 2 );
+ // Calendar and Story Budget.
+ add_filter( 'ef_calendar_item_actions', [ $this, 'filter_post_row_actions' ], 10, 2 );
+ add_filter( 'ef_story_budget_item_actions', [ $this, 'filter_post_row_actions' ], 10, 2 );
+ }
- // Add necessary capabilities to allow management of notifications
- $notifications_roles = array(
- 'administrator' => array('edit_post_subscriptions'),
- 'editor' => array('edit_post_subscriptions'),
- 'author' => array('edit_post_subscriptions'),
- );
+ // Ajax for saving notification updates.
+ add_action( 'wp_ajax_save_notifications', [ $this, 'ajax_save_post_subscriptions' ] );
+ add_action( 'wp_ajax_ef_notifications_user_post_subscription', [ $this, 'handle_user_post_subscription' ] );
+ }
- foreach ( $notifications_roles as $role => $caps ) {
- $this->add_caps_to_role( $role, $caps );
+ /**
+ * 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 notifications.
+ $notifications_roles = [
+ 'administrator' => [ 'edit_post_subscriptions' ],
+ 'editor' => [ 'edit_post_subscriptions' ],
+ 'author' => [ 'edit_post_subscriptions' ],
+ ];
+
+ foreach ( $notifications_roles as $role => $caps ) {
+ $this->add_caps_to_role( $role, $caps );
+ }
}
-
- }
- /**
- * Upgrade our data in case we need to
- *
- * @since 0.7
- */
- function upgrade( $previous_version ) {
- global $edit_flow;
-
- // Upgrade path to v0.7
- if ( version_compare( $previous_version, '0.7' , '<' ) ) {
- // Migrate whether notifications were enabled or not
- if ( $enabled = get_option( 'edit_flow_notifications_enabled' ) )
- $enabled = 'on';
- else
- $enabled = 'off';
- $edit_flow->update_module_option( $this->module->name, 'enabled', $enabled );
- delete_option( 'edit_flow_notifications_enabled' );
- // Migrate whether to always notify the admin
- if ( $always_notify_admin = get_option( 'edit_flow_always_notify_admin' ) )
- $always_notify_admin = 'on';
- else
- $always_notify_admin = 'off';
- $edit_flow->update_module_option( $this->module->name, 'always_notify_admin', $always_notify_admin );
- delete_option( 'edit_flow_always_notify_admin' );
-
- // 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 our data in case we need to.
+ *
+ * @since 0.7
+ *
+ * @param string $previous_version The previous plugin version.
+ */
+ public function upgrade( $previous_version ) {
+ global $edit_flow;
+
+ // Upgrade path to v0.7.
+ if ( version_compare( $previous_version, '0.7', '<' ) ) {
+ // Migrate whether notifications were enabled or not.
+ $enabled = get_option( 'edit_flow_notifications_enabled' );
+ if ( $enabled ) {
+ $enabled = 'on';
+ } else {
+ $enabled = 'off';
+ }
+ $edit_flow->update_module_option( $this->module->name, 'enabled', $enabled );
+ delete_option( 'edit_flow_notifications_enabled' );
+ // Migrate whether to always notify the admin.
+ $always_notify_admin = get_option( 'edit_flow_always_notify_admin' );
+ if ( $always_notify_admin ) {
+ $always_notify_admin = 'on';
+ } else {
+ $always_notify_admin = 'off';
+ }
+ $edit_flow->update_module_option( $this->module->name, 'always_notify_admin', $always_notify_admin );
+ delete_option( 'edit_flow_always_notify_admin' );
+
+ // 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 );
+ }
}
-
- }
-
- /**
- * Register the taxonomies we use to manage relationships
- *
- * @since 0.7
- *
- * @uses register_taxonomy()
- */
- function register_taxonomies() {
-
- // Load the currently supported post types so we only register against those
- $supported_post_types = $this->get_post_types_for_module( $this->module );
-
- $args = array(
- 'hierarchical' => false,
- 'update_count_callback' => '_update_post_term_count',
- 'label' => false,
- 'query_var' => false,
- 'rewrite' => false,
- 'public' => false,
- 'show_ui' => false
- );
- register_taxonomy( $this->following_users_taxonomy, $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() ) {
- wp_enqueue_script( 'jquery-listfilterizer' );
- wp_enqueue_script( 'jquery-quicksearch' );
- wp_enqueue_script( 'edit-flow-notifications-js', $this->module_url . 'lib/notifications.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch' ), EDIT_FLOW_VERSION, true );
- wp_localize_script(
- 'edit-flow-notifications-js',
- 'ef_notifications_localization',
- array(
- 'no_access' => esc_html__( 'No Access', 'edit-flow' ),
- 'no_email' => esc_html__( 'No Email', 'edit-flow' )
- )
- );
+
+ /**
+ * Register the taxonomies we use to manage relationships.
+ *
+ * @since 0.7
+ *
+ * @uses register_taxonomy()
+ */
+ public function register_taxonomies() {
+
+ // Load the currently supported post types so we only register against those.
+ $supported_post_types = $this->get_post_types_for_module( $this->module );
+
+ $args = [
+ 'hierarchical' => false,
+ 'update_count_callback' => '_update_post_term_count',
+ 'label' => false,
+ 'query_var' => false,
+ 'rewrite' => false,
+ 'public' => false,
+ 'show_ui' => false,
+ ];
+ register_taxonomy( $this->following_users_taxonomy, $supported_post_types, $args );
}
- }
-
- /**
- * 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-notifications-css', $this->module->module_url . 'lib/notifications.css', false, EDIT_FLOW_VERSION );
+
+ /**
+ * Enqueue necessary admin scripts.
+ *
+ * @since 0.7
+ *
+ * @uses wp_enqueue_script()
+ */
+ public function enqueue_admin_scripts() {
+ global $post;
+
+ if ( $this->is_post_management_page( $this->module->name ) ) {
+ wp_enqueue_script( 'jquery-listfilterizer' );
+ wp_enqueue_script( 'edit-flow-notifications-js', $this->module_url . 'lib/notifications.js', [ 'jquery', 'jquery-listfilterizer' ], EDIT_FLOW_VERSION, true );
+
+ $localization_data = [
+ 'no_access' => esc_html__( 'No Access', 'edit-flow' ),
+ 'no_email' => esc_html__( 'No Email', 'edit-flow' ),
+ 'post_author' => esc_html__( 'Post Author', 'edit-flow' ),
+ 'auto_subscribed' => esc_html__( 'Auto-subscribed', 'edit-flow' ),
+ ];
+
+ // Add post author info if we're on a post edit screen.
+ if ( $post ) {
+ $localization_data['post_author_id'] = (int) $post->post_author;
+ $localization_data['post_author_auto_subscribe'] = apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'subscription_action' );
+
+ // Check if post author is currently a follower.
+ $followers = $this->get_following_users( $post->ID, 'id' );
+ $localization_data['post_author_is_following'] = in_array( (int) $post->post_author, $followers, true );
+ }
+
+ wp_localize_script(
+ 'edit-flow-notifications-js',
+ 'ef_notifications_localization',
+ $localization_data
+ );
+ }
}
- }
- /**
- * JS required for the Follow link to work
- *
- * @since 0.8
- */
- public function action_admin_head_follow_js() {
- ?>
-
+ post_type, $this->get_post_types_for_module( $this->module ) ) )
- return $actions;
+ if ( ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) ) {
+ return $actions;
+ }
- if ( ! current_user_can( $this->edit_post_subscriptions_cap ) || ! current_user_can( 'edit_post', $post->ID ) )
- return $actions;
+ if ( ! current_user_can( $this->edit_post_subscriptions_cap ) || ! current_user_can( 'edit_post', $post->ID ) ) {
+ return $actions;
+ }
- $parts = $this->get_follow_action_parts( $post );
- $actions['ef_follow_link'] = '
' . $parts['text'] . '';
+ $parts = $this->get_follow_action_parts( $post );
- return $actions;
- }
+ $actions['ef_follow_link'] = '
' . $parts['text'] . '';
- /**
- * Get an action parts for a user to follow or unfollow a post
- *
- * @since 0.8
- */
- private function get_follow_action_parts( $post ) {
+ return $actions;
+ }
- $args = array(
- 'action' => 'ef_notifications_user_post_subscription',
- 'post_id' => $post->ID,
- );
- $following_users = $this->get_following_users( $post->ID );
- if ( in_array( wp_get_current_user()->user_login, $following_users ) ) {
- $args['method'] = 'unfollow';
- $title_text = __( 'Click to unfollow updates to this post', 'edit-flow' );
- $follow_text = __( 'Following', 'edit-flow' );
- } else {
- $args['method'] = 'follow';
- $title_text = __( 'Follow updates to this post', 'edit-flow' );
- $follow_text = __( 'Follow', 'edit-flow' );
+ /**
+ * Get an action parts for a user to follow or unfollow a post.
+ *
+ * @since 0.8
+ *
+ * @param WP_Post $post The post object.
+ * @return array The action parts array.
+ */
+ private function get_follow_action_parts( $post ) {
+ $args = [
+ 'action' => 'ef_notifications_user_post_subscription',
+ 'post_id' => $post->ID,
+ ];
+
+ $following_users = $this->get_following_users( $post->ID );
+ if ( in_array( wp_get_current_user()->user_login, $following_users ) ) {
+ $args['method'] = 'unfollow';
+ $title_text = __( 'Click to unfollow updates to this post', 'edit-flow' );
+ $follow_text = __( 'Following', 'edit-flow' );
+ } else {
+ $args['method'] = 'follow';
+ $title_text = __( 'Follow updates to this post', 'edit-flow' );
+ $follow_text = __( 'Follow', 'edit-flow' );
+ }
+
+ // wp_nonce_url() has encoding issues: http://core.trac.wordpress.org/ticket/20771.
+ $args['_wpnonce'] = wp_create_nonce( 'ef_notifications_user_post_subscription' );
+
+ return [
+ 'title' => $title_text,
+ 'text' => $follow_text,
+ 'link' => add_query_arg( $args, admin_url( 'admin-ajax.php' ) ),
+ ];
}
- // wp_nonce_url() has encoding issues: http://core.trac.wordpress.org/ticket/20771
- $args['_wpnonce'] = wp_create_nonce( 'ef_notifications_user_post_subscription' );
+ /**
+ * Add the subscriptions meta box to relevant post types.
+ */
+ public function add_post_meta_box() {
- return array(
- 'title' => $title_text,
- 'text' => $follow_text,
- 'link' => add_query_arg( $args, admin_url( 'admin-ajax.php' ) ),
- );
- }
-
- /**
- * Add the subscriptions meta box to relevant post types
- */
- function add_post_meta_box() {
-
- if ( !current_user_can( $this->edit_post_subscriptions_cap ) )
- return;
-
- $usergroup_post_types = $this->get_post_types_for_module( $this->module );
- foreach ( $usergroup_post_types as $post_type ) {
- add_meta_box( 'edit-flow-notifications', __( 'Notifications', 'edit-flow'), array( $this, 'notifications_meta_box'), $post_type, 'advanced' );
+ if ( ! current_user_can( $this->edit_post_subscriptions_cap ) ) {
+ return;
+ }
+
+ $usergroup_post_types = $this->get_post_types_for_module( $this->module );
+ foreach ( $usergroup_post_types as $post_type ) {
+ add_meta_box( 'edit-flow-notifications', __( 'Notifications', 'edit-flow' ), [ $this, 'notifications_meta_box' ], $post_type, 'advanced' );
+ }
}
- }
-
- /**
- * Outputs box used to subscribe users and usergroups to Posts
- *
- * @todo add_cap to set subscribers for posts; default to Admin and editors
- */
- function notifications_meta_box() {
- global $post, $post_ID, $edit_flow;
- ?>
-
-
-
-
-
-
- get_following_users( $post->ID, 'id' );
- $select_form_args = array(
- 'list_class' => 'ef-post_following_list',
- );
- $this->users_select_form( $followers, $select_form_args ); ?>
-
-
- module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) ): ?>
-
-
- get_following_usergroups( $post->ID, 'ids' );
- $edit_flow->user_groups->usergroups_select_form( $following_usergroups ); ?>
+
+ /**
+ * Outputs box used to subscribe users and usergroups to Posts
+ *
+ * @todo add_cap to set subscribers for posts; default to Admin and editors
+ */
+ public function notifications_meta_box() {
+ global $post, $post_ID, $edit_flow;
+ ?>
+
+
+
+
+
+
+ get_following_users( $post->ID, 'id' );
+ $select_form_args = [
+ 'list_class' => 'ef-post_following_list',
+ ];
+ $this->users_select_form( $followers, $select_form_args );
+ ?>
+
+
+ module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) ) : ?>
+
+
+ get_following_usergroups( $post->ID, 'ids' );
+ $edit_flow->user_groups->usergroups_select_form( $following_usergroups );
+ ?>
+
+
+
+
+
-
-
-
-
-
-
- user_can_be_notified( get_user_by( 'id', $user_id ), $post->ID )) {
- // span.post_following_list-no_access is also added in notifications.js after AJAX that ticks/unticks a user
- echo '
' . esc_html__( 'No Access', 'edit-flow' ) . '';
- }
-
- // Add No Email span if they have no email
- $user_object = get_user_by( 'id', $user_id );
- if ( !is_a( $user_object, 'WP_User') OR empty( $user_object->user_email ) ) {
- // span.post_following_list-no_email is also added in notifications.js after AJAX that ticks/unticks a user
- echo '
' . esc_html__( 'No Email', 'edit-flow' ) . '';
+ post_author === (int) $user_id );
+ $auto_subscribe_on = apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'subscription_action' );
+
+ // Show "Post Author" badge for the post author.
+ if ( $is_post_author ) {
+ echo '
' . esc_html__( 'Post Author', 'edit-flow' ) . '';
+ }
+
+ // Show "Auto-subscribed" badge if post author is auto-subscribed.
+ if ( $is_post_author && $auto_subscribe_on && $checked ) {
+ echo '
' . esc_html__( 'Auto-subscribed', 'edit-flow' ) . '';
+ }
+
+ // Only show warning badges if user is subscribed.
+ if ( ! $checked ) {
+ return;
+ }
+
+ // Add No Access span if they won't be notified.
+ if ( ! $this->user_can_be_notified( get_user_by( 'id', $user_id ), $post->ID ) ) {
+ // span.post_following_list-no_access is also added in notifications.js after AJAX that ticks/unticks a user.
+ echo '
' . esc_html__( 'No Access', 'edit-flow' ) . '';
+ }
+
+ // Add No Email span if they have no email.
+ $user_object = get_user_by( 'id', $user_id );
+ if ( ! is_a( $user_object, 'WP_User' ) || empty( $user_object->user_email ) ) {
+ // span.post_following_list-no_email is also added in notifications.js after AJAX that ticks/unticks a user.
+ echo '
' . esc_html__( 'No Email', 'edit-flow' ) . '';
+ }
}
- $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
- $post = get_post( $post_id );
+ /**
+ * Called when a notification editorial metadata checkbox is checked. Handles saving of a user/usergroup to a post.
+ */
+ public function ajax_save_post_subscriptions() {
+ global $edit_flow;
+
+ // Verify nonce.
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce().
+ if ( ! isset( $_POST['_nonce'] ) || ! wp_verify_nonce( $_POST['_nonce'], 'save_user_usergroups' ) ) {
+ wp_die( esc_html__( 'Nonce check failed. Please ensure you can add users or user groups to a post.', 'edit-flow' ) );
+ }
+
+ $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
+ $post = get_post( $post_id );
+
+ $valid_post = ! is_null( $post ) && ! wp_is_post_revision( $post_id ) && ! wp_is_post_autosave( $post_id );
+ if ( ! isset( $_POST['ef_notifications_name'] ) || ! $valid_post || ! current_user_can( $this->edit_post_subscriptions_cap ) ) {
+ wp_die();
+ }
+
+ $user_group_ids = [];
+ if ( isset( $_POST['user_group_ids'] ) && is_array( $_POST['user_group_ids'] ) ) {
+ $user_group_ids = array_map( 'intval', $_POST['user_group_ids'] );
+ }
+
+ if ( 'ef-selected-users[]' === $_POST['ef_notifications_name'] ) {
+ // Prevent auto-subscribing users that have opted out of notifications.
+ add_filter( 'ef_notification_auto_subscribe_current_user', '__return_false', PHP_INT_MAX );
+ $this->save_post_following_users( $post, $user_group_ids );
+
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['post_id'] ) ) {
+
+ // Determine if any of the selected users won't have notification access.
+ $subscribers_with_no_access = array_filter(
+ $user_group_ids,
+ function ( $user_id ) use ( $post_id ) {
+ return ! $this->user_can_be_notified( get_user_by( 'id', $user_id ), $post_id );
+ }
+ );
+
+ // Determine if any of the selected users are missing their emails.
+ $subscribers_with_no_email = [];
+ foreach ( $user_group_ids as $user_id ) {
+ $user_object = get_user_by( 'id', $user_id );
+ if ( ! is_a( $user_object, 'WP_User' ) || empty( $user_object->user_email ) ) {
+ $subscribers_with_no_email[] = $user_id;
+ }
+ }
+
+ // Assemble the JSON reply with various lists of problematic users.
+ $json_success = [
+ 'subscribers_with_no_access' => array_values( $subscribers_with_no_access ),
+ 'subscribers_with_no_email' => array_values( $subscribers_with_no_email ),
+ ];
+
+ wp_send_json_success( $json_success );
+ }
+ // Remove auto-subscribe prevention behavior from earlier.
+ remove_filter( 'ef_notification_auto_subscribe_current_user', '__return_false', PHP_INT_MAX );
+ }
+
+ $groups_enabled = $this->module_enabled( 'user_groups' ) && in_array( get_post_type( $post_id ), $this->get_post_types_for_module( $edit_flow->user_groups->module ) );
+ if ( 'following_usergroups[]' === $_POST['ef_notifications_name'] && $groups_enabled ) {
+ $this->save_post_following_usergroups( $post, $user_group_ids );
+ }
- $valid_post = ! is_null( $post ) && ! wp_is_post_revision( $post_id ) && ! wp_is_post_autosave( $post_id );
- if ( ! isset( $_POST['ef_notifications_name'] ) || ! $valid_post || ! current_user_can( $this->edit_post_subscriptions_cap ) ) {
- die();
+ wp_die();
}
- $user_group_ids = array();
- if ( isset( $_POST['user_group_ids'] ) && is_array( $_POST['user_group_ids'] ) ) {
- $user_group_ids = array_map( 'intval', $_POST['user_group_ids'] );
+ /**
+ * Handle a request to update a user's post subscription.
+ *
+ * @since 0.8
+ */
+ public function handle_user_post_subscription() {
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce().
+ if ( ! empty( $_GET['_wpnonce'] ) && ! wp_verify_nonce( $_GET['_wpnonce'], 'ef_notifications_user_post_subscription' ) ) {
+ $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
+ }
+
+ if ( ! current_user_can( $this->edit_post_subscriptions_cap ) ) {
+ $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
+ }
+
+ $post_id = isset( $_GET['post_id'] ) ? (int) $_GET['post_id'] : 0;
+ $post = get_post( $post_id );
+
+ if ( ! $post ) {
+ $this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
+ }
+
+ if ( isset( $_GET['method'] ) && 'follow' == $_GET['method'] ) {
+ $retval = $this->follow_post_user( $post, get_current_user_id() );
+ } else {
+ $retval = $this->unfollow_post_user( $post, get_current_user_id() );
+ }
+
+ if ( is_wp_error( $retval ) ) {
+ $this->print_ajax_response( 'error', $retval->get_error_message() );
+ }
+
+ $this->print_ajax_response( 'success', (object) $this->get_follow_action_parts( $post ) );
}
- if ( 'ef-selected-users[]' === $_POST['ef_notifications_name'] ) {
- // Prevent auto-subscribing users that have opted out of notifications.
- add_filter( 'ef_notification_auto_subscribe_current_user', '__return_false', PHP_INT_MAX );
- $this->save_post_following_users( $post, $user_group_ids );
-
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['post_id'] ) ) {
-
- // Determine if any of the selected users won't have notification access
- $subscribers_with_no_access = array_filter( $user_group_ids, function( $user_id ) {
- return ! $this->user_can_be_notified( get_user_by( 'id', $user_id ), $_POST['post_id'] );
- } );
-
- // Determine if any of the selected users are missing their emails
- $subscribers_with_no_email = array();
- foreach ( $user_group_ids AS $user_id ) {
- $user_object = get_user_by( 'id', $user_id );
- if ( !is_a( $user_object, 'WP_User') OR empty( $user_object->user_email ) ) {
- $subscribers_with_no_email[] = $user_id;
- }
+
+ /**
+ * Called when post is saved. Handles saving of user/usergroup followers.
+ *
+ * @param string $new_status The new post status.
+ * @param string $old_status The old post status.
+ * @param WP_Post $post The post object.
+ */
+ public function save_post_subscriptions( $new_status, $old_status, $post ) {
+ global $edit_flow;
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce().
+ if ( ! empty( $_POST['_wpnonce'] ) && ! wp_verify_nonce( $_POST['_wpnonce'], 'editpost' ) ) {
+ $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
+ }
+
+ // Only if has edit_post_subscriptions cap.
+ if ( ( ! wp_is_post_revision( $post ) && ! wp_is_post_autosave( $post ) ) && isset( $_POST['ef-save_followers'] ) && current_user_can( $this->edit_post_subscriptions_cap ) ) {
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized when saved.
+ $users = isset( $_POST['ef-selected-users'] ) ? $_POST['ef-selected-users'] : [];
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized when saved.
+ $usergroups = isset( $_POST['following_usergroups'] ) ? $_POST['following_usergroups'] : [];
+ $this->save_post_following_users( $post, $users );
+ if ( $this->module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) ) {
+ $this->save_post_following_usergroups( $post, $usergroups );
}
-
- // Assemble the json reply with various lists of problematic users
- $json_success = array(
- 'subscribers_with_no_access' => array_values( $subscribers_with_no_access ),
- 'subscribers_with_no_email' => array_values( $subscribers_with_no_email ),
- );
-
- wp_send_json_success( $json_success );
}
- // Remove auto-subscribe prevention behavior from earlier.
- remove_filter( 'ef_notification_auto_subscribe_current_user', '__return_false', PHP_INT_MAX );
}
-
- $groups_enabled = $this->module_enabled( 'user_groups' ) && in_array( get_post_type( $post_id ), $this->get_post_types_for_module( $edit_flow->user_groups->module ) );
- if ( 'following_usergroups[]' === $_POST['ef_notifications_name'] && $groups_enabled ) {
- $this->save_post_following_usergroups( $post, $user_group_ids );
+
+ /**
+ * Sets users to follow specified post.
+ *
+ * @param int|WP_Post $post The post ID or object.
+ * @param array|null $users Array of user IDs to follow the post.
+ */
+ public function save_post_following_users( $post, $users = null ) {
+ if ( ! is_array( $users ) ) {
+ $users = [];
+ }
+
+ // Add current user to following users.
+ $user = wp_get_current_user();
+ if ( $user && apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'subscription_action' ) ) {
+ $users[] = $user->ID;
+ }
+
+ // Add post author to following users.
+ if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'subscription_action' ) ) {
+ $users[] = $post->post_author;
+ }
+
+ $users = array_unique( array_map( 'intval', $users ) );
+
+ $follow = $this->follow_post_user( $post, $users, false );
}
- die();
- }
+ /**
+ * Sets usergroups to follow specified post.
+ *
+ * @param int|WP_Post $post The post ID or object.
+ * @param array|null $usergroups Usergroups to follow posts.
+ */
+ public function save_post_following_usergroups( $post, $usergroups = null ) {
- /**
- * Handle a request to update a user's post subscription
- *
- * @since 0.8
- */
- public function handle_user_post_subscription() {
+ if ( ! is_array( $usergroups ) ) {
+ $usergroups = [];
+ }
+ $usergroups = array_map( 'intval', $usergroups );
- if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'ef_notifications_user_post_subscription' ) )
- $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
+ $follow = $this->follow_post_usergroups( $post, $usergroups, false );
+ }
- if ( ! current_user_can( $this->edit_post_subscriptions_cap ) )
- $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
+ /**
+ * Set up and send post status change notification email.
+ *
+ * @param string $new_status The new post status.
+ * @param string $old_status The old post status.
+ * @param WP_Post $post The post object.
+ */
+ public function notification_status_change( $new_status, $old_status, $post ) {
+ global $edit_flow;
- $post = get_post( ( $post_id = $_GET['post_id'] ) );
+ // Kill switch for notification.
+ if ( ! apply_filters( 'ef_notification_status_change', $new_status, $old_status, $post ) || ! apply_filters( "ef_notification_{$post->post_type}_status_change", $new_status, $old_status, $post ) ) {
+ return false;
+ }
- if ( ! $post )
- $this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
+ $supported_post_types = $this->get_post_types_for_module( $this->module );
+ if ( ! in_array( $post->post_type, $supported_post_types ) ) {
+ return;
+ }
- if ( 'follow' == $_GET['method'] )
- $retval = $this->follow_post_user( $post, get_current_user_id() );
- else
- $retval = $this->unfollow_post_user( $post, get_current_user_id() );
+ // No need to notify if it's a revision, auto-draft, or if post status wasn't changed.
+ $ignored_statuses = apply_filters( 'ef_notification_ignored_statuses', [ $old_status, 'inherit', 'auto-draft' ], $post->post_type );
- if ( is_wp_error( $retval ) )
- $this->print_ajax_response( 'error', $retval->get_error_message() );
+ if ( ! in_array( $new_status, $ignored_statuses ) ) {
- $this->print_ajax_response( 'success', (object)$this->get_follow_action_parts( $post ) );
- }
+ // Get current user.
+ $current_user = wp_get_current_user();
+ $post_author = get_userdata( $post->post_author );
- /**
- * Called when post is saved. Handles saving of user/usergroup followers
- *
- * @param int $post ID of the post
- */
- function save_post_subscriptions( $new_status, $old_status, $post ) {
- global $edit_flow;
- // only if has edit_post_subscriptions cap
- if( ( !wp_is_post_revision($post) && !wp_is_post_autosave($post) ) && isset($_POST['ef-save_followers']) && current_user_can( $this->edit_post_subscriptions_cap ) ) {
- $users = isset( $_POST['ef-selected-users'] ) ? $_POST['ef-selected-users'] : array();
- $usergroups = isset( $_POST['following_usergroups'] ) ? $_POST['following_usergroups'] : array();
- $this->save_post_following_users( $post, $users );
- if ( $this->module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) )
- $this->save_post_following_usergroups( $post, $usergroups );
+ $blogname = get_option( 'blogname' );
+
+ $body = '';
+
+ $post_id = $post->ID;
+ $post_title = ef_draft_or_post_title( $post_id );
+ $post_type = get_post_type_object( $post->post_type )->labels->singular_name;
+
+ if ( 0 != $current_user->ID ) {
+ $current_user_display_name = $current_user->display_name;
+ $current_user_email = sprintf( '(%s)', $current_user->user_email );
+ } else {
+ $current_user_display_name = __( 'WordPress Scheduler', 'edit-flow' );
+ $current_user_email = '';
+ }
+
+ $old_status_post_obj = get_post_status_object( $old_status );
+ $new_status_post_obj = get_post_status_object( $new_status );
+ $old_status_friendly_name = '';
+ $new_status_friendly_name = '';
+
+ /*
+ * The get_post_status_object() function will return null for certain statuses (i.e., 'new').
+ * The mega if/else block below should catch all cases, but just in case, we
+ * make sure to at least set $old_status_friendly_name and $new_status_friendly_name
+ * to an empty string to ensure they're at least set.
+ *
+ * Then, we attempt to set them to a sensible default before we start the
+ * mega if/else block.
+ */
+ if ( ! is_null( $old_status_post_obj ) ) {
+ $old_status_friendly_name = $old_status_post_obj->label;
+ }
+
+ if ( ! is_null( $new_status_post_obj ) ) {
+ $new_status_friendly_name = $new_status_post_obj->label;
+ }
+
+ // Email subject and first line of body.
+ // Set message subjects according to what action is being taken on the Post.
+ if ( 'new' == $old_status || 'auto-draft' == $old_status ) {
+ $old_status_friendly_name = 'New';
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] New %2$s Created: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
+ $body .= sprintf( __( 'A new %1$s (#%2$s "%3$s") was created by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user->display_name, $current_user->user_email ) . "\r\n";
+ } elseif ( 'trash' == $new_status ) {
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] %2$s Trashed: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
+ $body .= sprintf( __( '%1$s #%2$s "%3$s" was moved to the trash by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
+ } elseif ( 'trash' == $old_status ) {
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] %2$s Restored (from Trash): "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
+ $body .= sprintf( __( '%1$s #%2$s "%3$s" was restored from trash by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
+ } elseif ( 'future' == $new_status ) {
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] %2$s Scheduled: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email 6. scheduled date */
+ $body .= sprintf( __( '%1$s #%2$s "%3$s" was scheduled by %4$s %5$s. It will be published on %6$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email, $this->get_scheduled_datetime( $post ) ) . "\r\n";
+ } elseif ( 'publish' == $new_status ) {
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] %2$s Published: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
+ $body .= sprintf( __( '%1$s #%2$s "%3$s" was published by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
+ } elseif ( 'publish' == $old_status ) {
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] %2$s Unpublished: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
+ $body .= sprintf( __( '%1$s #%2$s "%3$s" was unpublished by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
+ } else {
+ /* translators: 1: site name, 2: post type, 3. post title */
+ $subject = sprintf( __( '[%1$s] %2$s Status Changed for "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
+ /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
+ $body .= sprintf( __( 'Status was changed for %1$s #%2$s "%3$s" by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
+ }
+
+ /* translators: 1: date, 2: time, 3: timezone */
+ $body .= sprintf( __( 'This action was taken on %1$s at %2$s %3$s', 'edit-flow' ), date_i18n( get_option( 'date_format' ) ), date_i18n( get_option( 'time_format' ) ), get_option( 'timezone_string' ) ) . "\r\n";
+
+ // Email body.
+ $body .= "\r\n";
+ /* translators: 1: old status, 2: new status */
+ $body .= sprintf( __( '%1$s => %2$s', 'edit-flow' ), $old_status_friendly_name, $new_status_friendly_name );
+ $body .= "\r\n\r\n";
+
+ $body .= "--------------------\r\n\r\n";
+
+ /* translators: 1: post type */
+ $body .= sprintf( __( '== %s Details ==', 'edit-flow' ), $post_type ) . "\r\n";
+ /* translators: 1: post title */
+ $body .= sprintf( __( 'Title: %s', 'edit-flow' ), $post_title ) . "\r\n";
+ if ( ! empty( $post_author ) ) {
+ /* translators: 1: author name, 2: author email */
+ $body .= sprintf( __( 'Author: %1$s (%2$s)', 'edit-flow' ), $post_author->display_name, $post_author->user_email ) . "\r\n";
+ }
+
+ $edit_post_link = get_edit_post_link( $post_id );
+ if ( is_null( $edit_post_link ) ) {
+ return;
+ }
+
+ $edit_link = htmlspecialchars_decode( $edit_post_link );
+
+ if ( 'publish' != $new_status ) {
+ $view_link = add_query_arg( [ 'preview' => 'true' ], wp_get_shortlink( $post_id ) );
+ } else {
+ $permalink = get_permalink( $post_id );
+ if ( is_null( $permalink ) ) {
+ return;
+ }
+
+ $view_link = htmlspecialchars_decode( $permalink );
+ }
+
+ $body .= "\r\n";
+ $body .= __( '== Actions ==', 'edit-flow' ) . "\r\n";
+ /* translators: 1: edit link */
+ $body .= sprintf( __( 'Add editorial comment: %s', 'edit-flow' ), $edit_link . '#editorialcomments/add' ) . "\r\n";
+ /* translators: 1: edit link */
+ $body .= sprintf( __( 'Edit: %s', 'edit-flow' ), $edit_link ) . "\r\n";
+ /* translators: 1: view link */
+ $body .= sprintf( __( 'View: %s', 'edit-flow' ), $view_link ) . "\r\n";
+
+ $body .= $this->get_notification_footer( $post );
+
+ $this->send_email( 'status-change', $post, $subject, $body );
+
+ if ( 'on' === $this->module->options->send_to_webhook ) {
+ /* translators: 1: user name, 2: post type, 3: post id, 4: edit link, 5: post title, 6: old status, 7: new status */
+ $format = __( '*%1$s* changed the status of *%2$s #%3$s - <%4$s|%5$s>* from *%6$s* to *%7$s*', 'edit-flow' );
+ $text = sprintf( $format, $current_user->display_name, $post_type, $post_id, $edit_link, $post_title, $old_status_friendly_name, $new_status_friendly_name );
+
+ $this->send_to_webhook( $text, 'status-change', $current_user, $post );
+ }
+ }
}
-
- }
-
- /**
- * Sets users to follow specified post
- *
- * @param int $post ID of the post
- */
- function save_post_following_users( $post, $users = null ) {
- if( !is_array( $users ) )
- $users = array();
-
- // Add current user to following users
- $user = wp_get_current_user();
- if ( $user && apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'subscription_action' ) )
- $users[] = $user->ID;
-
- // Add post author to following users
- if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'subscription_action' ) )
- $users[] = $post->post_author;
-
- $users = array_unique( array_map( 'intval', $users ) );
-
- $follow = $this->follow_post_user( $post, $users, false );
-
- }
-
- /**
- * Sets usergroups to follow specified post
- *
- * @param int $post ID of the post
- * @param array $usergroups Usergroups to follow posts
- */
- function save_post_following_usergroups( $post, $usergroups = null ) {
-
- if( !is_array($usergroups) ) $usergroups = array();
- $usergroups = array_map( 'intval', $usergroups );
-
- $follow = $this->follow_post_usergroups($post, $usergroups, false);
- }
-
- /**
- * Set up and send post status change notification email
- */
- function notification_status_change( $new_status, $old_status, $post ) {
- global $edit_flow;
- // Kill switch for notification
- if ( ! apply_filters( 'ef_notification_status_change', $new_status, $old_status, $post ) || ! apply_filters( "ef_notification_{$post->post_type}_status_change", $new_status, $old_status, $post ) )
- return false;
-
- $supported_post_types = $this->get_post_types_for_module( $this->module );
- if ( !in_array( $post->post_type, $supported_post_types ) )
- return;
-
- // No need to notify if it's a revision, auto-draft, or if post status wasn't changed
- $ignored_statuses = apply_filters( 'ef_notification_ignored_statuses', array( $old_status, 'inherit', 'auto-draft' ), $post->post_type );
-
- if ( !in_array( $new_status, $ignored_statuses ) ) {
-
- // Get current user
+ /**
+ * Set up and set editorial comment notification email.
+ *
+ * @param WP_Comment $comment The editorial comment object.
+ * @return boolean|null|void False if notification is disabled, null/void otherwise.
+ */
+ public function notification_comment( $comment ) {
+
+ $post = get_post( $comment->comment_post_ID );
+
+ $supported_post_types = $this->get_post_types_for_module( $this->module );
+ if ( ! in_array( $post->post_type, $supported_post_types ) ) {
+ return;
+ }
+
+ // Kill switch for notification.
+ if ( ! apply_filters( 'ef_notification_editorial_comment', $comment, $post ) ) {
+ return false;
+ }
+
+ $user = get_userdata( $post->post_author );
$current_user = wp_get_current_user();
-
- $post_author = get_userdata( $post->post_author );
- //$duedate = $edit_flow->post_metadata->get_post_meta($post->ID, 'duedate', true);
-
- $blogname = get_option('blogname');
-
- $body = '';
-
- $post_id = $post->ID;
+
+ $post_id = $post->ID;
+ $post_type = get_post_type_object( $post->post_type )->labels->singular_name;
$post_title = ef_draft_or_post_title( $post_id );
- $post_type = get_post_type_object( $post->post_type )->labels->singular_name;
- if( 0 != $current_user->ID ) {
- $current_user_display_name = $current_user->display_name;
- $current_user_email = sprintf( '(%s)', $current_user->user_email );
- } else {
- $current_user_display_name = __( 'WordPress Scheduler', 'edit-flow' );
- $current_user_email = '';
+ // Fetch the text list of people who were notified from comment meta.
+ // @see EF_Editorial_Comments->maybe_output_comment_meta().
+ $notification_list = get_comment_meta( $comment->comment_ID, 'notification_list', true );
+
+ // Set user to follow post, but make it filterable.
+ if ( apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'comment' ) ) {
+ $this->follow_post_user( $post, (int) $current_user->ID );
+ }
+
+ // Set the post author to follow the post but make it filterable.
+ if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'comment' ) ) {
+ $this->follow_post_user( $post, (int) $post->post_author );
+ }
+
+ $blogname = get_option( 'blogname' );
+
+ /* translators: 1: blog name, 2: post title */
+ $subject = sprintf( __( '[%1$s] New Editorial Comment: "%2$s"', 'edit-flow' ), $blogname, $post_title );
+
+ /* translators: 1: post id, 2: post title, 3. post type */
+ $body = sprintf( __( 'A new editorial comment was added to %3$s #%1$s "%2$s"', 'edit-flow' ), $post_id, $post_title, $post_type ) . "\r\n\r\n";
+ /* translators: 1: comment author, 2: author email, 3: date, 4: time */
+ $body .= sprintf( __( '%1$s (%2$s) said on %3$s at %4$s:', 'edit-flow' ), $current_user->display_name, $current_user->user_email, mysql2date( get_option( 'date_format' ), $comment->comment_date ), mysql2date( get_option( 'time_format' ), $comment->comment_date ) ) . "\r\n";
+ $body .= "\r\n" . $comment->comment_content . "\r\n";
+
+ $body .= "\r\n--------------------\r\n";
+ // Insert the notification list from comment meta.
+ // @see EF_Editorial_Comments->maybe_output_comment_meta().
+ if ( $notification_list ) {
+ $body .= esc_html__( 'Notified', 'edit-flow' ) . ': ' . esc_html( $notification_list ) . "\n";
}
- $old_status_post_obj = get_post_status_object( $old_status );
- $new_status_post_obj = get_post_status_object( $new_status );
- $old_status_friendly_name = '';
- $new_status_friendly_name = '';
+ $edit_post_link = get_edit_post_link( $post_id );
+ if ( is_null( $edit_post_link ) ) {
+ return;
+ }
- /**
- * get_post_status_object will return null for certain statuses (i.e., 'new')
- * The mega if/else block below should catch all cases, but just in case, we
- * make sure to at least set $old_status_friendly_name and $new_status_friendly_name
- * to an empty string to ensure they're at least set.
- *
- * Then, we attempt to set them to a sensible default before we start the
- * mega if/else block
- */
- if ( ! is_null( $old_status_post_obj ) ) {
- $old_status_friendly_name = $old_status_post_obj->label;
- }
-
- if ( ! is_null( $new_status_post_obj ) ) {
- $new_status_friendly_name = $new_status_post_obj->label;
- }
-
- // Email subject and first line of body
- // Set message subjects according to what action is being taken on the Post
- if ( $old_status == 'new' || $old_status == 'auto-draft' ) {
- $old_status_friendly_name = "New";
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __( '[%1$s] New %2$s Created: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
- $body .= sprintf( __( 'A new %1$s (#%2$s "%3$s") was created by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user->display_name, $current_user->user_email ) . "\r\n";
- } else if ( $new_status == 'trash' ) {
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __( '[%1$s] %2$s Trashed: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
- $body .= sprintf( __( '%1$s #%2$s "%3$s" was moved to the trash by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
- } else if ( $old_status == 'trash' ) {
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __( '[%1$s] %2$s Restored (from Trash): "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
- $body .= sprintf( __( '%1$s #%2$s "%3$s" was restored from trash by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
- } else if ( $new_status == 'future' ) {
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __('[%1$s] %2$s Scheduled: "%3$s"'), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email 6. scheduled date */
- $body .= sprintf( __( '%1$s #%2$s "%3$s" was scheduled by %4$s %5$s. It will be published on %6$s' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email, $this->get_scheduled_datetime( $post ) ) . "\r\n";
- } else if ( $new_status == 'publish' ) {
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __( '[%1$s] %2$s Published: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
- $body .= sprintf( __( '%1$s #%2$s "%3$s" was published by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
- } else if ( $old_status == 'publish' ) {
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __( '[%1$s] %2$s Unpublished: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
- $body .= sprintf( __( '%1$s #%2$s "%3$s" was unpublished by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
- } else {
- /* translators: 1: site name, 2: post type, 3. post title */
- $subject = sprintf( __( '[%1$s] %2$s Status Changed for "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
- /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
- $body .= sprintf( __( 'Status was changed for %1$s #%2$s "%3$s" by %4$s %5$s', 'edit-flow'), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
- }
-
- /* translators: 1: date, 2: time, 3: timezone */
- $body .= sprintf( __( 'This action was taken on %1$s at %2$s %3$s', 'edit-flow' ), date_i18n( get_option( 'date_format' ) ), date_i18n( get_option( 'time_format' ) ), get_option( 'timezone_string' ) ) . "\r\n";
-
- // Email body
- $body .= "\r\n";
- /* translators: 1: old status, 2: new status */
- $body .= sprintf( __( '%1$s => %2$s', 'edit-flow' ), $old_status_friendly_name, $new_status_friendly_name );
- $body .= "\r\n\r\n";
-
- $body .= "--------------------\r\n\r\n";
-
- $body .= sprintf( __( '== %s Details ==', 'edit-flow' ), $post_type ) . "\r\n";
- $body .= sprintf( __( 'Title: %s', 'edit-flow' ), $post_title ) . "\r\n";
- if ( ! empty( $post_author ) ) {
- /* translators: 1: author name, 2: author email */
- $body .= sprintf( __( 'Author: %1$s (%2$s)', 'edit-flow' ), $post_author->display_name, $post_author->user_email ) . "\r\n";
- }
-
- $edit_link = htmlspecialchars_decode( get_edit_post_link( $post_id ) );
- if ( $new_status != 'publish' ) {
- $view_link = add_query_arg( array( 'preview' => 'true' ), wp_get_shortlink( $post_id ) );
- } else {
- $view_link = htmlspecialchars_decode( get_permalink( $post_id ) );
+ $edit_link = htmlspecialchars_decode( $edit_post_link );
+
+ $permalink = get_permalink( $post_id );
+ if ( is_null( $permalink ) ) {
+ return;
}
+
+ $view_link = htmlspecialchars_decode( $permalink );
+
$body .= "\r\n";
$body .= __( '== Actions ==', 'edit-flow' ) . "\r\n";
+ /* translators: 1: edit link */
+ $body .= sprintf( __( 'Reply: %s', 'edit-flow' ), $edit_link . '#editorialcomments/reply/' . $comment->comment_ID ) . "\r\n";
+ /* translators: 1: edit link */
$body .= sprintf( __( 'Add editorial comment: %s', 'edit-flow' ), $edit_link . '#editorialcomments/add' ) . "\r\n";
+ /* translators: 1: edit link */
$body .= sprintf( __( 'Edit: %s', 'edit-flow' ), $edit_link ) . "\r\n";
+ /* translators: 1: view link */
$body .= sprintf( __( 'View: %s', 'edit-flow' ), $view_link ) . "\r\n";
-
- $body .= $this->get_notification_footer($post);
-
- $this->send_email( 'status-change', $post, $subject, $body );
- }
-
- }
-
- /**
- * Set up and set editorial comment notification email
- *
- * @param WP_Comment $comment
- * @return boolean|null|void
- */
- function notification_comment( $comment ) {
-
- $post = get_post($comment->comment_post_ID);
-
- $supported_post_types = $this->get_post_types_for_module( $this->module );
- if ( !in_array( $post->post_type, $supported_post_types ) )
- return;
-
- // Kill switch for notification
- if ( ! apply_filters( 'ef_notification_editorial_comment', $comment, $post ) )
- return false;
-
- $user = get_userdata( $post->post_author );
- $current_user = wp_get_current_user();
-
- $post_id = $post->ID;
- $post_type = get_post_type_object( $post->post_type )->labels->singular_name;
- $post_title = ef_draft_or_post_title( $post_id );
-
- // Fetch the text list of people who were notified from comment meta @see EF_Editorial_Comments->maybe_output_comment_meta()
- $notification_list = get_comment_meta( $comment->comment_ID, 'notification_list', true );
-
- // Check if this a reply
- //$parent_ID = isset( $comment->comment_parent_ID ) ? $comment->comment_parent_ID : 0;
- //if($parent_ID) $parent = get_comment($parent_ID);
-
- // Set user to follow post, but make it filterable
- if ( apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'comment' ) )
- $this->follow_post_user($post, (int) $current_user->ID);
-
- // Set the post author to follow the post but make it filterable
- if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'comment' ) )
- $this->follow_post_user( $post, (int) $post->post_author );
-
- $blogname = get_option('blogname');
-
- /* translators: 1: blog name, 2: post title */
- $subject = sprintf( __( '[%1$s] New Editorial Comment: "%2$s"', 'edit-flow' ), $blogname, $post_title );
-
- /* translators: 1: post id, 2: post title, 3. post type */
- $body = sprintf( __( 'A new editorial comment was added to %3$s #%1$s "%2$s"', 'edit-flow' ), $post_id, $post_title, $post_type ) . "\r\n\r\n";
- /* translators: 1: comment author, 2: author email, 3: date, 4: time */
- $body .= sprintf( __( '%1$s (%2$s) said on %3$s at %4$s:', 'edit-flow' ), $current_user->display_name, $current_user->user_email, mysql2date(get_option('date_format'), $comment->comment_date), mysql2date(get_option('time_format'), $comment->comment_date) ) . "\r\n";
- $body .= "\r\n" . $comment->comment_content . "\r\n";
-
- // @TODO: mention if it was a reply
- /*
- if($parent) {
-
+
+ /* translators: 1: post type */
+ $body .= "\r\n" . sprintf( __( 'You can see all editorial comments on this %s here: ', 'edit-flow' ), $post_type ) . "\r\n";
+ $body .= $edit_link . '#editorialcomments' . "\r\n\r\n";
+
+ $body .= $this->get_notification_footer( $post );
+
+ $this->send_email( 'comment', $post, $subject, $body );
+
+ if ( 'on' === $this->module->options->send_to_webhook ) {
+ /* translators: 1: comment author, 2: post type, 3: post id, 4: edit link, 5: post title, 6: comment content */
+ $format = __( '*%1$s* left a comment on *%2$s #%3$s - <%4$s|%5$s>*', 'edit-flow' ) . "\n\n";
+ $format .= '%6$s';
+ $text = sprintf( $format, $comment->comment_author, $post_type, $post_id, $edit_link, $post_title, $comment->comment_content );
+
+ $this->send_to_webhook( $text, 'comment', $current_user, $comment );
+ }
}
- */
-
-
- $body .= "\r\n--------------------\r\n";
- // Insert the notification list from comment meta @see EF_Editorial_Comments->maybe_output_comment_meta()
- if ($notification_list) {
- $body .= esc_html__( 'Notified', 'edit-flow' ) . ": " . esc_html( $notification_list ) . "\n";
+
+ /**
+ * Get the notification email footer.
+ *
+ * @param WP_Post $post The post object.
+ * @return string The email footer content.
+ */
+ public function get_notification_footer( $post ) {
+ $body = '';
+ $body .= "\r\n--------------------\r\n";
+ /* translators: 1: post title */
+ $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";
+ // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Intentional use for email timestamp.
+ /* translators: 1: date */
+ $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;
}
-
- $edit_link = htmlspecialchars_decode( get_edit_post_link( $post_id ) );
- $view_link = htmlspecialchars_decode( get_permalink( $post_id ) );
-
- $body .= "\r\n";
- $body .= __( '== Actions ==', 'edit-flow' ) . "\r\n";
- $body .= sprintf( __( 'Reply: %s', 'edit-flow' ), $edit_link . '#editorialcomments/reply/' . $comment->comment_ID ) . "\r\n";
- $body .= sprintf( __( 'Add editorial comment: %s', 'edit-flow' ), $edit_link . '#editorialcomments/add' ) . "\r\n";
- $body .= sprintf( __( 'Edit: %s', 'edit-flow' ), $edit_link ) . "\r\n";
- $body .= sprintf( __( 'View: %s', 'edit-flow' ), $view_link ) . "\r\n";
-
- $body .= "\r\n" . sprintf( __( 'You can see all editorial comments on this %s here: ', 'edit-flow' ), $post_type ). "\r\n";
- $body .= $edit_link . "#editorialcomments" . "\r\n\r\n";
-
- $body .= $this->get_notification_footer($post);
-
- $this->send_email( 'comment', $post, $subject, $body );
- }
-
- function get_notification_footer( $post ) {
- $body = "";
- $body .= "\r\n--------------------\r\n";
- $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";
- $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;
- }
-
- /**
- * send_email()
- */
- function send_email( $action, $post, $subject, $message, $message_headers = '' ) {
-
- // Get list of email recipients -- set them CC
- $recipients = $this->_get_notification_recipients( $post, true );
-
- if( $recipients && ! is_array( $recipients ) )
- $recipients = explode( ',', $recipients );
-
- $subject = apply_filters( 'ef_notification_send_email_subject', $subject, $action, $post );
- $message = apply_filters( 'ef_notification_send_email_message', $message, $action, $post );
- $message_headers = apply_filters( 'ef_notification_send_email_message_headers', $message_headers, $action, $post );
-
- if( EF_NOTIFICATION_USE_CRON ) {
- $this->schedule_emails( $recipients, $subject, $message, $message_headers );
- } else if ( !empty( $recipients ) ) {
- foreach( $recipients as $recipient ) {
- $this->send_single_email( $recipient, $subject, $message, $message_headers );
+
+ /**
+ * Send email notification.
+ *
+ * @param string $action The notification action type.
+ * @param WP_Post $post The post object.
+ * @param string $subject The email subject.
+ * @param string $message The email message body.
+ * @param string $message_headers Optional email headers.
+ */
+ public function send_email( $action, $post, $subject, $message, $message_headers = '' ) {
+
+ // Get list of email recipients -- set them CC.
+ $recipients = $this->_get_notification_recipients( $post, true );
+
+ if ( $recipients && ! is_array( $recipients ) ) {
+ $recipients = explode( ',', $recipients );
+ }
+
+ $subject = apply_filters( 'ef_notification_send_email_subject', $subject, $action, $post );
+ $message = apply_filters( 'ef_notification_send_email_message', $message, $action, $post );
+ $message_headers = apply_filters( 'ef_notification_send_email_message_headers', $message_headers, $action, $post );
+
+ if ( EF_NOTIFICATION_USE_CRON ) {
+ $this->schedule_emails( $recipients, $subject, $message, $message_headers );
+ } elseif ( ! empty( $recipients ) ) {
+ foreach ( $recipients as $recipient ) {
+ $this->send_single_email( $recipient, $subject, $message, $message_headers );
+ }
}
}
- }
-
- /**
- * Schedules emails to be sent in succession
- *
- * @param mixed $recipients Individual email or array of emails
- * @param string $subject Subject of the email
- * @param string $message Body of the email
- * @param string $message_headers. (optional) Message headers
- * @param int $time_offset (optional) Delay in seconds per email
- */
- function schedule_emails( $recipients, $subject, $message, $message_headers = '', $time_offset = 1 ) {
- $recipients = (array) $recipients;
-
- $send_time = time();
-
- foreach( $recipients as $recipient ) {
- wp_schedule_single_event( $send_time, 'ef_send_scheduled_email', array( $recipient, $subject, $message, $message_headers ) );
- $send_time += $time_offset;
+
+ /**
+ * Send notifications to Slack.
+ *
+ * @param string $message Message to be sent to webhook.
+ * @param string $action Action being taken. Currently only `status-change` and `comment`.
+ * @param WP_User $user User who is taking the action.
+ * @param WP_Post|WP_Comment $post Post or comment that the action is being taken on.
+ */
+ public function send_to_webhook( $message, $action, $user, $post ) {
+ $webhook_url = $this->module->options->webhook_url;
+
+ // Bail if the webhook URL is not set.
+ if ( empty( $webhook_url ) ) {
+ return;
+ }
+
+ // Set up the payload.
+ $payload = [
+ 'text' => $message,
+ ];
+
+ // Apply filters to the payload.
+ $payload = apply_filters( 'ef_notification_send_to_webhook_payload', $payload, $action, $user, $post );
+
+ // Send the notification.
+ $response = wp_remote_post(
+ $webhook_url,
+ [
+ 'body' => wp_json_encode( $payload ),
+ 'headers' => [ 'Content-Type' => 'application/json' ],
+ ]
+ );
+ if ( is_wp_error( $response ) ) {
+ $this->print_ajax_response( 'error', 'Unable to send notification to webhook provided', 400 );
+ }
}
-
- }
-
- /**
- * Sends an individual email
- *
- * @param mixed $to Email to send to
- * @param string $subject Subject of the email
- * @param string $message Body of the email
- * @param string $message_headers. (optional) Message headers
- */
- function send_single_email( $to, $subject, $message, $message_headers = '' ) {
- wp_mail( $to, $subject, $message, $message_headers );
- }
- /**
- * Returns a list of recipients for a given post.
- *
- * @param WP_Post $post
- * @param bool $string Whether to return recipients as comma-delimited string or array.
- * @return string|array Recipients to receive notification.
- */
- private function _get_notification_recipients( $post, $string = false ) {
- global $edit_flow;
+ /**
+ * Schedules emails to be sent in succession.
+ *
+ * @param mixed $recipients Individual email or array of emails.
+ * @param string $subject Subject of the email.
+ * @param string $message Body of the email.
+ * @param string $message_headers Optional. Message headers.
+ * @param int $time_offset Optional. Delay in seconds per email.
+ */
+ public function schedule_emails( $recipients, $subject, $message, $message_headers = '', $time_offset = 1 ) {
+ $recipients = (array) $recipients;
+
+ $send_time = time();
- $post_id = $post->ID;
- if ( ! $post_id ) {
- return $string ? '' : array();
+ foreach ( $recipients as $recipient ) {
+ wp_schedule_single_event( $send_time, 'ef_send_scheduled_email', [ $recipient, $subject, $message, $message_headers ] );
+ $send_time += $time_offset;
+ }
}
- // Email all admins if enabled.
- $admins = array();
- if ( 'on' === $this->module->options->always_notify_admin ) {
- $admins[] = get_option('admin_email');
+ /**
+ * Sends an individual email.
+ *
+ * @param mixed $to Email to send to.
+ * @param string $subject Subject of the email.
+ * @param string $message Body of the email.
+ * @param string $message_headers Optional. Message headers.
+ */
+ public function send_single_email( $to, $subject, $message, $message_headers = '' ) {
+ // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_mail_wp_mail -- Notification feature requires email.
+ wp_mail( $to, $subject, $message, $message_headers );
}
- $usergroup_recipients = array();
- if ( $this->module_enabled( 'user_groups' ) ) {
- $usergroups = $this->get_following_usergroups( $post_id, 'ids' );
- foreach ( (array) $usergroups as $usergroup_id ) {
- $usergroup = $edit_flow->user_groups->get_usergroup_by( 'id', $usergroup_id );
- foreach ( (array) $usergroup->user_ids as $user_id ) {
- $usergroup_user = get_user_by( 'id', $user_id );
- if ( $this->user_can_be_notified( $usergroup_user, $post_id ) ) {
- $usergroup_recipients[] = $usergroup_user->user_email;
+ /**
+ * Returns a list of recipients for a given post.
+ *
+ * @param WP_Post $post The post object.
+ * @param bool $return_string Whether to return recipients as comma-delimited string or array.
+ * @return string|array Recipients to receive notification.
+ */
+ private function _get_notification_recipients( $post, $return_string = false ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Legacy method name.
+ global $edit_flow;
+
+ $post_id = $post->ID;
+ if ( ! $post_id ) {
+ return $return_string ? '' : [];
+ }
+
+ // Email all admins if enabled.
+ $admins = [];
+ if ( 'on' === $this->module->options->always_notify_admin ) {
+ $admins[] = get_option( 'admin_email' );
+ }
+
+ $usergroup_recipients = [];
+ if ( $this->module_enabled( 'user_groups' ) ) {
+ $usergroups = $this->get_following_usergroups( $post_id, 'ids' );
+ foreach ( (array) $usergroups as $usergroup_id ) {
+ $usergroup = $edit_flow->user_groups->get_usergroup_by( 'id', $usergroup_id );
+ foreach ( (array) $usergroup->user_ids as $user_id ) {
+ $usergroup_user = get_user_by( 'id', $user_id );
+ if ( $this->user_can_be_notified( $usergroup_user, $post_id ) ) {
+ $usergroup_recipients[] = $usergroup_user->user_email;
+ }
}
}
}
- }
- $user_recipients = $this->get_following_users( $post_id, 'user_email' );
- foreach( $user_recipients as $key => $user ) {
- $user_object = get_user_by( 'email', $user );
- if ( ! $this->user_can_be_notified( $user_object, $post_id ) ) {
- unset( $user_recipients[ $key ] );
+ $user_recipients = $this->get_following_users( $post_id, 'user_email' );
+ foreach ( $user_recipients as $key => $user ) {
+ $user_object = get_user_by( 'email', $user );
+ if ( ! $this->user_can_be_notified( $user_object, $post_id ) ) {
+ unset( $user_recipients[ $key ] );
+ }
+ }
+
+ // Merge arrays, filter any duplicates, and remove empty entries.
+ $recipients = array_filter( array_unique( array_merge( $admins, $user_recipients, $usergroup_recipients ) ) );
+
+ // Process the recipients for this email to be sent.
+ foreach ( $recipients as $key => $user_email ) {
+ // Don't send the email to the current user unless we've explicitly indicated they should receive it.
+ if ( false === apply_filters( 'ef_notification_email_current_user', false ) && wp_get_current_user()->user_email == $user_email ) {
+ unset( $recipients[ $key ] );
+ }
}
- }
- // Merge arrays, filter any duplicates, and remove empty entries.
- $recipients = array_filter( array_unique( array_merge( $admins, $user_recipients, $usergroup_recipients ) ) );
+ /**
+ * Filters the list of notification recipients.
+ *
+ * @param array $recipients List of recipient email addresses.
+ * @param WP_Post $post The post object.
+ * @param bool $return_string True if the recipients list will later be returned as a string.
+ */
+ $recipients = apply_filters( 'ef_notification_recipients', $recipients, $post, $return_string );
- // Process the recipients for this email to be sent.
- foreach( $recipients as $key => $user_email ) {
- // Don't send the email to the current user unless we've explicitly indicated they should receive it.
- if ( false === apply_filters( 'ef_notification_email_current_user', false ) && wp_get_current_user()->user_email == $user_email ) {
- unset( $recipients[ $key ] );
+ // If string set to true, return comma-delimited.
+ if ( $return_string && is_array( $recipients ) ) {
+ return implode( ',', $recipients );
+ } else {
+ return $recipients;
}
}
/**
- * Filters the list of notification recipients.
+ * Check if a user can be notified.
+ *
+ * This is based off of the ability to edit the post/page by default.
*
- * @param array $recipients List of recipient email addresses.
- * @param WP_Post $post
- * @param bool $string True if the recipients list will later be returned as a string.
+ * @since 0.8.3
+ *
+ * @param WP_User $user The user object.
+ * @param int $post_id The post ID.
+ * @return bool True if the user can be notified, false otherwise.
*/
- $recipients = apply_filters( 'ef_notification_recipients', $recipients, $post, $string );
-
- // If string set to true, return comma-delimited.
- if ( $string && is_array( $recipients ) ) {
- return implode( ',', $recipients );
- } else {
- return $recipients;
- }
- }
+ public function user_can_be_notified( $user, $post_id ) {
+ $can_be_notified = false;
- /**
- * Check if a user can be notified.
- * This is based off of the ability to edit the post/page by default.
- *
- * @since 0.8.3
- * @param WP_User $user
- * @param int $post_id
- * @return bool True if the user can be notified, false otherwise.
- */
- function user_can_be_notified( $user, $post_id ) {
- $can_be_notified = false;
+ if ( $user instanceof WP_User && is_user_member_of_blog( $user->ID ) && is_numeric( $post_id ) ) {
+ // The 'edit_post' cap check also covers the undocumented 'edit_page' cap.
+ $can_be_notified = $user->has_cap( 'edit_post', $post_id );
+ }
- if ( $user instanceof WP_User && is_user_member_of_blog( $user->ID ) && is_numeric( $post_id ) ) {
- // The 'edit_post' cap check also covers the undocumented 'edit_page' cap.
- $can_be_notified = $user->has_cap( 'edit_post', $post_id );
+ /**
+ * Filters if a user can be notified. Defaults to true if they can edit the post/page.
+ *
+ * @param bool $can_be_notified True if the user can be notified.
+ * @param WP_User|bool $user The user object, otherwise false.
+ * @param int $post_id The post the user will be notified about.
+ */
+ return (bool) apply_filters( 'ef_notification_user_can_be_notified', $can_be_notified, $user, $post_id );
}
/**
- * Filters if a user can be notified. Defaults to true if they can edit the post/page.
+ * Set a user or users to follow a post.
+ *
+ * @param int|object $post Post object or ID.
+ * @param string|array $users User or users to subscribe to post updates.
+ * @param bool $append Whether users should be added to following_users list or replace existing list.
*
- * @param bool $can_be_notified True if the user can be notified.
- * @param WP_User|bool $user The user object, otherwise false.
- * @param int $post_id The post the user will be notified about.
+ * @return true|WP_Error True on success, WP_Error on failure.
*/
- return (bool) apply_filters( 'ef_notification_user_can_be_notified', $can_be_notified, $user, $post_id );
- }
+ public function follow_post_user( $post, $users, $append = true ) {
- /**
- * Set a user or users to follow a post
- *
- * @param int|object $post Post object or ID
- * @param string|array $users User or users to subscribe to post updates
- * @param bool $append Whether users should be added to following_users list or replace existing list
- *
- * @return true|WP_Error $response True on success, WP_Error on failure
- */
- function follow_post_user( $post, $users, $append = true ) {
+ $post = get_post( $post );
+ if ( ! $post ) {
+ return new WP_Error( 'missing-post', $this->module->messages['missing-post'] );
+ }
- $post = get_post( $post );
- if ( ! $post )
- return new WP_Error( 'missing-post', $this->module->messages['missing-post'] );
+ if ( ! is_array( $users ) ) {
+ $users = [ $users ];
+ }
- if ( ! is_array( $users ) )
- $users = array( $users );
+ $user_terms = [];
+ foreach ( $users as $user ) {
+ if ( is_int( $user ) ) {
+ $user = get_user_by( 'id', $user );
+ } elseif ( is_string( $user ) ) {
+ $user = get_user_by( 'login', $user );
+ }
- $user_terms = array();
- foreach( $users as $user ) {
+ if ( ! is_object( $user ) ) {
+ continue;
+ }
- if ( is_int( $user ) )
- $user = get_user_by( 'id', $user );
- elseif ( is_string( $user ) )
- $user = get_user_by( 'login', $user );
+ $name = $user->user_login;
- if ( ! is_object( $user ) )
- continue;
+ // Add user as a term if they don't exist.
+ $term = $this->add_term_if_not_exists( $name, $this->following_users_taxonomy );
- $name = $user->user_login;
+ if ( ! is_wp_error( $term ) ) {
+ $user_terms[] = $name;
+ }
+ }
+ $set = wp_set_object_terms( $post->ID, $user_terms, $this->following_users_taxonomy, $append );
- // Add user as a term if they don't exist
- $term = $this->add_term_if_not_exists( $name, $this->following_users_taxonomy );
-
- if ( ! is_wp_error( $term ) ) {
- $user_terms[] = $name;
+ if ( is_wp_error( $set ) ) {
+ return $set;
+ } else {
+ return true;
}
}
- $set = wp_set_object_terms( $post->ID, $user_terms, $this->following_users_taxonomy, $append );
-
- if ( is_wp_error( $set ) )
- return $set;
- else
- return true;
- }
- /**
- * Removes user from following_users taxonomy for the given Post,
- * so they no longer receive future notifications.
- *
- * @param object $post Post object or ID
- * @param int|string|array $users One or more users to unfollow from the post
- * @return true|WP_Error $response True on success, WP_Error on failure
- */
- function unfollow_post_user( $post, $users ) {
+ /**
+ * Removes user from following_users taxonomy for the given Post,
+ * so they no longer receive future notifications.
+ *
+ * @param object $post Post object or ID.
+ * @param int|string|array $users One or more users to unfollow from the post.
+ * @return true|WP_Error True on success, WP_Error on failure.
+ */
+ public function unfollow_post_user( $post, $users ) {
- $post = get_post( $post );
- if ( ! $post )
- return new WP_Error( 'missing-post', $this->module->messages['missing-post'] );
+ $post = get_post( $post );
+ if ( ! $post ) {
+ return new WP_Error( 'missing-post', $this->module->messages['missing-post'] );
+ }
- if ( ! is_array( $users ) )
- $users = array( $users );
+ if ( ! is_array( $users ) ) {
+ $users = [ $users ];
+ }
- $terms = get_the_terms( $post->ID, $this->following_users_taxonomy );
- if ( is_wp_error( $terms ) )
- return $terms;
+ $terms = get_the_terms( $post->ID, $this->following_users_taxonomy );
+ if ( is_wp_error( $terms ) ) {
+ return $terms;
+ }
- $user_terms = wp_list_pluck( $terms, 'slug' );
- foreach( $users as $user ) {
+ $user_terms = wp_list_pluck( $terms, 'slug' );
+ foreach ( $users as $user ) {
+ if ( is_int( $user ) ) {
+ $user = get_user_by( 'id', $user );
+ } elseif ( is_string( $user ) ) {
+ $user = get_user_by( 'login', $user );
+ }
- if ( is_int( $user ) )
- $user = get_user_by( 'id', $user );
- elseif ( is_string( $user ) )
- $user = get_user_by( 'login', $user );
+ if ( ! is_object( $user ) ) {
+ continue;
+ }
- if ( ! is_object( $user ) )
- continue;
+ $key = array_search( $user->user_login, $user_terms );
+ if ( false !== $key ) {
+ unset( $user_terms[ $key ] );
+ }
+ }
+ $set = wp_set_object_terms( $post->ID, $user_terms, $this->following_users_taxonomy, false );
- $key = array_search( $user->user_login, $user_terms );
- if ( false !== $key )
- unset( $user_terms[$key] );
+ if ( is_wp_error( $set ) ) {
+ return $set;
+ } else {
+ return true;
+ }
}
- $set = wp_set_object_terms( $post->ID, $user_terms, $this->following_users_taxonomy, false );
- if ( is_wp_error( $set ) )
- return $set;
- else
- return true;
- }
+ /**
+ * Set usergroups to follow a post.
+ *
+ * @param int|WP_Post $post Post object or ID.
+ * @param array|int $usergroups Usergroup IDs to follow the post.
+ * @param bool $append Whether to append to or replace existing usergroups.
+ */
+ public function follow_post_usergroups( $post, $usergroups = 0, $append = true ) {
+ if ( ! $this->module_enabled( 'user_groups' ) ) {
+ return;
+ }
- /**
- * follow_post_usergroups()
- *
- */
- function follow_post_usergroups( $post, $usergroups = 0, $append = true ) {
- if ( !$this->module_enabled( 'user_groups' ) )
- return;
+ $post_id = ( is_int( $post ) ) ? $post : $post->ID;
+
+ if ( ! is_array( $usergroups ) ) {
+ $usergroups = [ $usergroups ];
+ }
- $post_id = ( is_int($post) ) ? $post : $post->ID;
- if( !is_array($usergroups) )
- $usergroups = array($usergroups);
+ // Make sure each usergroup id is an integer and not a number stored as a string.
+ foreach ( $usergroups as $key => $usergroup ) {
+ $usergroups[ $key ] = intval( $usergroup );
+ }
- // make sure each usergroup id is an integer and not a number stored as a string
- foreach( $usergroups as $key => $usergroup ) {
- $usergroups[$key] = intval($usergroup);
+ wp_set_object_terms( $post_id, $usergroups, $this->following_usergroups_taxonomy, $append );
}
- wp_set_object_terms( $post_id, $usergroups, $this->following_usergroups_taxonomy, $append );
- return;
- }
-
- /**
- * Removes users that are deleted from receiving future notifications (i.e. makes them unfollow posts FOREVER!)
- *
- * @param $id int ID of the user
- */
- function delete_user_action( $id ) {
- if( !$id ) return;
-
- // get user data
- $user = get_userdata($id);
-
- if( $user ) {
- // Delete term from the following_users taxonomy
- $user_following_term = get_term_by('name', $user->user_login, $this->following_users_taxonomy);
- if( $user_following_term ) wp_delete_term($user_following_term->term_id, $this->following_users_taxonomy);
+ /**
+ * Removes users that are deleted from receiving future notifications.
+ *
+ * Makes them unfollow posts FOREVER!
+ *
+ * @param int $id ID of the user.
+ */
+ public function delete_user_action( $id ) {
+ if ( ! $id ) {
+ return;
+ }
+
+ // Get user data.
+ $user = get_userdata( $id );
+
+ if ( $user ) {
+ // Delete term from the following_users taxonomy.
+ $user_following_term = get_term_by( 'name', $user->user_login, $this->following_users_taxonomy );
+ if ( $user_following_term ) {
+ wp_delete_term( $user_following_term->term_id, $this->following_users_taxonomy );
+ }
+ }
}
- return;
- }
-
- /**
- * Add user as a term if they aren't already
- * @param $term string term to be added
- * @param $taxonomy string taxonomy to add term to
- * @return WP_error if insert fails, true otherwise
- */
- function add_term_if_not_exists( $term, $taxonomy ) {
- if ( !term_exists($term, $taxonomy) ) {
- $args = array( 'slug' => sanitize_title($term) );
- return wp_insert_term( $term, $taxonomy, $args );
+
+ /**
+ * Add user as a term if they aren't already.
+ *
+ * @param string $term Term to be added.
+ * @param string $taxonomy Taxonomy to add term to.
+ * @return array|WP_Error|true WP_Error if insert fails, term array on insert, true if exists.
+ */
+ public function add_term_if_not_exists( $term, $taxonomy ) {
+ if ( ! term_exists( $term, $taxonomy ) ) {
+ $args = [ 'slug' => sanitize_title( $term ) ];
+ return wp_insert_term( $term, $taxonomy, $args );
+ }
+ return true;
}
- return true;
- }
-
- /**
- * Gets a list of the users following the specified post
- *
- * @param int $post_id The ID of the post
- * @param string $return The field to return
- * @return array $users Users following the specified posts
- */
- function get_following_users( $post_id, $return = 'user_login' ) {
- // Get following_users terms for the post
- $users = wp_get_object_terms( $post_id, $this->following_users_taxonomy, array('fields' => 'names') );
+ /**
+ * Gets a list of the users following the specified post.
+ *
+ * @param int $post_id The ID of the post.
+ * @param string $return_field The field to return.
+ * @return array Users following the specified posts.
+ */
+ public function get_following_users( $post_id, $return_field = 'user_login' ) {
+
+ // Get following_users terms for the post.
+ $users = wp_get_object_terms( $post_id, $this->following_users_taxonomy, [ 'fields' => 'names' ] );
- // Don't have any following users
- if( !$users || is_wp_error($users) )
- return array();
+ // Don't have any following users.
+ if ( ! $users || is_wp_error( $users ) ) {
+ return [];
+ }
- // if just want user_login, return as is
- if ( $return == 'user_login' )
- return $users;
+ // If just want user_login, return as is.
+ if ( 'user_login' == $return_field ) {
+ return $users;
+ }
- foreach( (array)$users as $key => $user ) {
- switch( $user ) {
- case is_int( $user ):
- $search = 'id';
- break;
- case is_email( $user ):
- $search = 'email';
- break;
- default:
- $search = 'login';
- break;
- }
- $new_user = get_user_by( $search, $user );
- if ( ! $new_user || ! is_user_member_of_blog( $new_user->ID ) ) {
- unset( $users[ $key ] );
- continue;
- }
- switch( $return ) {
- case 'user_login':
- $users[$key] = $new_user->user_login;
- break;
- case 'id':
- $users[$key] = $new_user->ID;
- break;
- case 'user_email':
- $users[$key] = $new_user->user_email;
- break;
+ foreach ( (array) $users as $key => $user ) {
+ switch ( $user ) {
+ case is_int( $user ):
+ $search = 'id';
+ break;
+ case is_email( $user ):
+ $search = 'email';
+ break;
+ default:
+ $search = 'login';
+ break;
+ }
+ $new_user = get_user_by( $search, $user );
+ if ( ! $new_user || ! is_user_member_of_blog( $new_user->ID ) ) {
+ unset( $users[ $key ] );
+ continue;
+ }
+ switch ( $return_field ) {
+ case 'user_login':
+ $users[ $key ] = $new_user->user_login;
+ break;
+ case 'id':
+ $users[ $key ] = $new_user->ID;
+ break;
+ case 'user_email':
+ $users[ $key ] = $new_user->user_email;
+ break;
+ }
+ }
+ if ( ! $users || is_wp_error( $users ) ) {
+ $users = [];
}
+ return $users;
}
- if( !$users || is_wp_error($users) )
- $users = array();
- return $users;
- }
-
- /**
- * Gets a list of the usergroups that are following specified post
- *
- * @param int $post_id
- * @return array $usergroups All of the usergroup slugs
- */
- function get_following_usergroups( $post_id, $return = 'all' ) {
- global $edit_flow;
+ /**
+ * Gets a list of the usergroups that are following specified post.
+ *
+ * @param int $post_id The ID of the post.
+ * @param string $return_field The field to return.
+ * @return array All of the usergroup slugs.
+ */
+ public function get_following_usergroups( $post_id, $return_field = 'all' ) {
+ global $edit_flow;
- // Workaround for the fact that get_object_terms doesn't return just slugs
- if( $return == 'slugs' )
- $fields = 'all';
- else
- $fields = $return;
+ // Workaround for the fact that get_object_terms doesn't return just slugs.
+ if ( 'slugs' == $return_field ) {
+ $fields = 'all';
+ } else {
+ $fields = $return_field;
+ }
- $usergroups = wp_get_object_terms( $post_id, $this->following_usergroups_taxonomy, array( 'fields' => $fields ) );
+ $usergroups = wp_get_object_terms( $post_id, $this->following_usergroups_taxonomy, [ 'fields' => $fields ] );
- if( $return == 'slugs' ) {
- $slugs = array();
- foreach($usergroups as $usergroup) {
- $slugs[] = $usergroup->slug;
+ if ( 'slugs' == $return_field ) {
+ $slugs = [];
+ foreach ( $usergroups as $usergroup ) {
+ $slugs[] = $usergroup->slug;
+ }
+ $usergroups = $slugs;
}
- $usergroups = $slugs;
+ return $usergroups;
}
- return $usergroups;
- }
-
- /**
- * Gets a list of posts that a user is following
- *
- * @param string|int $user user_login or id of user
- * @param array $args
- * @return array $posts Posts a user is following
- */
- function get_user_following_posts( $user = 0, $args = null ) {
- if ( !$user )
- $user = (int) wp_get_current_user()->ID;
- if ( is_int($user) )
- $user = get_userdata($user)->user_login;
+ /**
+ * Gets a list of posts that a user is following.
+ *
+ * @param string|int $user User login or ID of user.
+ * @param array $args Query arguments.
+ * @return array Posts a user is following.
+ */
+ public function get_user_following_posts( $user = 0, $args = null ) {
+ if ( ! $user ) {
+ $user = (int) wp_get_current_user()->ID;
+ }
+
+ if ( is_int( $user ) ) {
+ $user = get_userdata( $user )->user_login;
+ }
- $post_args = array(
- 'tax_query' => array(
- array(
+ $post_args = [
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Required for user following functionality.
+ 'tax_query' => [
+ [
'taxonomy' => $this->following_users_taxonomy,
- 'field' => 'slug',
- 'terms' => $user,
- )
- ),
- 'posts_per_page' => '10',
- 'orderby' => 'modified',
- 'order' => 'DESC',
- 'post_status' => 'any',
- );
- $post_args = apply_filters( 'ef_user_following_posts_query_args', $post_args );
- $posts = get_posts( $post_args );
- return $posts;
+ 'field' => 'slug',
+ 'terms' => $user,
+ ],
+ ],
+ 'posts_per_page' => '10',
+ 'orderby' => 'modified',
+ 'order' => 'DESC',
+ 'post_status' => 'any',
+ ];
+ $post_args = apply_filters( 'ef_user_following_posts_query_args', $post_args );
+ $posts = get_posts( $post_args );
+ return $posts;
+ }
- }
-
- /**
- * 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
- */
- 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', __( 'Post types for notifications:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );
- add_settings_field( 'always_notify_admin', __( 'Always notify blog admin', 'edit-flow' ), array( $this, 'settings_always_notify_admin_option'), $this->module->options_group_name, $this->module->options_group_name . '_general' );
- }
-
- /**
- * Chose the post types for notifications
- *
- * @since 0.7
- */
- function settings_post_types_option() {
- global $edit_flow;
- $edit_flow->settings->helper_option_custom_post_type( $this->module );
- }
+ /**
+ * 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
+ */
+ 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', __( 'Post types for notifications:', 'edit-flow' ), [ $this, 'settings_post_types_option' ], $this->module->options_group_name, $this->module->options_group_name . '_general' );
+ add_settings_field( 'always_notify_admin', __( 'Always notify blog admin', 'edit-flow' ), [ $this, 'settings_always_notify_admin_option' ], $this->module->options_group_name, $this->module->options_group_name . '_general' );
+ add_settings_field( 'send_to_webhook', __( 'Send to Webhook', 'edit-flow' ), [ $this, 'settings_send_to_webhook' ], $this->module->options_group_name, $this->module->options_group_name . '_general' );
+ add_settings_field( 'webhook_url', __( 'Webhook URL', 'edit-flow' ), [ $this, 'settings_webhook_url' ], $this->module->options_group_name, $this->module->options_group_name . '_general' );
+ }
- /**
- * Option for whether the blog admin email address should be always notified or not
- *
- * @since 0.7
- */
- function settings_always_notify_admin_option() {
- $options = array(
- 'off' => __( 'Disabled', 'edit-flow' ),
- 'on' => __( 'Enabled', 'edit-flow' ),
- );
- echo '
';
- }
- /**
- * Validate our user input as the settings are being saved
- *
- * @since 0.7
- */
- 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 );
+ /**
+ * Option for whether the blog admin email address should be always notified or not
+ *
+ * @since 0.7
+ */
+ public function settings_always_notify_admin_option() {
+ $options = [
+ 'off' => __( 'Disabled', 'edit-flow' ),
+ 'on' => __( 'Enabled', 'edit-flow' ),
+ ];
+ echo '
';
+ }
- // Whitelist validation for the 'always_notify_admin' options
- if ( !isset( $new_options['always_notify_admin'] ) || $new_options['always_notify_admin'] != 'on' )
- $new_options['always_notify_admin'] = 'off';
-
- return $new_options;
+ /**
+ * Option to enable sending notifications to Slack
+ *
+ * @since 0.9.9
+ */
+ public function settings_send_to_webhook() {
+ $options = [
+ 'off' => __( 'Disabled', 'edit-flow' ),
+ 'on' => __( 'Enabled', 'edit-flow' ),
+ ];
+ echo '
';
+ }
- }
+ /**
+ * Option to set the Slack webhook URL.
+ *
+ * @since 0.9.9
+ */
+ public function settings_webhook_url() {
+ echo '
';
+ }
- /**
- * Settings page for notifications
- *
- * @since 0.7
- */
- function print_configure_view() {
- ?>
-
- module->settings_slug, false ) ); ?>" method="post">
+ module->options_group_name ); ?>
+ module->options_group_name ); ?>
+ module->name ) . '" />';
+ ?>
+
+
+ post_date );
-
- $date = date_i18n( get_option( 'date_format' ), $scheduled_ts );
- $time = date_i18n( get_option( 'time_format' ), $scheduled_ts );
-
- return sprintf( __( '%1$s at %2$s', 'edit-flow' ), $date, $time );
+ /**
+ * Gets a simple phrase containing the formatted date and time that the post is scheduled for.
+ *
+ * @since 0.8
+ *
+ * @param WP_Post $post Post object.
+ * @return string The scheduled datetime in human-readable format.
+ */
+ private function get_scheduled_datetime( $post ) {
+
+ $scheduled_ts = strtotime( $post->post_date );
+
+ $date = date_i18n( get_option( 'date_format' ), $scheduled_ts );
+ $time = date_i18n( get_option( 'time_format' ), $scheduled_ts );
+
+ /* translators: 1: date, 2: time */
+ return sprintf( __( '%1$s at %2$s', 'edit-flow' ), $date, $time );
+ }
}
-}
}
diff --git a/modules/settings/lib/settings.css b/modules/settings/lib/settings.css
index 80f451716..f1f6ea9db 100644
--- a/modules/settings/lib/settings.css
+++ b/modules/settings/lib/settings.css
@@ -1,5 +1,3 @@
-/* @override http://localhost:8888/wpplugindev/wp-content/plugins/edit-flow/css/settings.css?ver=0.6.4 */
-
.float-right {
float:right;
}
@@ -20,82 +18,84 @@
clear: both;
}
+/* Feature cards grid layout */
.edit-flow-modules {
- width: 100%;
- overflow: hidden;
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 15px;
margin-top: 20px;
- display: flex;
- flex-wrap: wrap;
+}
+
+/* Responsive grid for smaller screens */
+@media screen and (max-width: 1400px) {
+ .edit-flow-modules {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+@media screen and (max-width: 1100px) {
+ .edit-flow-modules {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media screen and (max-width: 782px) {
+ .edit-flow-modules {
+ grid-template-columns: 1fr;
+ }
}
.edit-flow-modules .edit-flow-module {
- width: 250px;
- min-height: 150px;
+ min-height: 180px;
position: relative;
- float: left;
- border: 1px solid #e1e1e1;
- padding: 10px;
- background-color: #f1f1f1;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
- margin-right: 15px;
- margin-bottom: 15px;
+ border: 1px solid #c3c4c7;
+ padding: 15px;
+ background-color: #fff;
+ border-radius: 4px;
}
.edit-flow-modules .edit-flow-module p {
- font-size: 12px;
- color: #3d3d3d;
- line-height: 150%;
- font-family: "Helvetica Neue",Helvetica,Arial,"Lucida Grande",Verdana,"Bitstream Vera Sans",sans-serif;
+ font-size: 14px;
+ color: #3c434a;
+ line-height: 1.6;
}
.edit-flow-modules .edit-flow-module h4 {
- color: #000000;
- font-family: "Helvetica Neue",Helvetica,Arial,"Lucida Grande",Verdana,"Bitstream Vera Sans",sans-serif;
- font-size: 17px;
- font-style: normal;
- font-weight: normal;
- line-height: 1.1;
- margin: 0;
- padding: 0;
+ color: #1d2327;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.4;
+ margin: 0 0 8px 0;
+ padding: 0;
}
.edit-flow-modules .edit-flow-module .edit-flow-module-actions {
position: absolute;
- bottom: 0px;
- left: 10px;
- width: 250px;
-}
-
-.edit-flow-modules .edit-flow-module .button-primary:hover {
- color: #FFFFFF;
- border-color: #3B5D0C;
-}
-
-.edit-flow-modules .edit-flow-module .button-primary.configure-edit-flow-module {
- color: #f1f1f1;
- border: solid 1px #666;
- background: #DDD;
- background: -webkit-gradient(linear, left top, left bottom, from(#BBB), to(#888));
- background: -moz-linear-gradient(top, #BBB, #888);
- box-shadow: none;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#BBB', endColorstr='#888');
- text-shadow: none;
- float: right;
+ bottom: 15px;
+ left: 15px;
+ right: 15px;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 12px;
}
-.edit-flow-modules .edit-flow-module .button-primary.enable-disable-edit-flow-module {
- float: left;
+/* Configure links as regular links instead of buttons */
+.edit-flow-modules .edit-flow-module a.configure-edit-flow-module {
+ color: #2271b1;
+ text-decoration: none;
+ font-size: 13px;
+ margin-right: auto;
}
-.edit-flow-modules .edit-flow-module .button-primary.configure-edit-flow-module:hover {
- color: #FFF;
- border-color: #444;
+.edit-flow-modules .edit-flow-module a.configure-edit-flow-module:hover {
+ color: #135e96;
+ text-decoration: underline;
}
-.edit-flow-admin a.cancel-settings-link {
- margin-left: 10px;
+/* Enable/Disable buttons on the right */
+.edit-flow-modules .edit-flow-module .enable-disable-edit-flow-module {
+ flex-shrink: 0;
}
.edit-flow-admin .inline-edit-row fieldset label span.title {
@@ -154,19 +154,6 @@
margin: 0;
}
-.edit-flow-modules .edit-flow-module a.button-primary {
- float: right;
-}
-
-.credits {
- color: #666666;
- font-size: 11px;
-}
-
-.credits a {
- color: #666666;
-}
-
/* Module specific image styles */
.button-inline-block {
@@ -194,32 +181,6 @@ h3.nav-tab-wrapper {
font-style: normal;
}
-.edit-flow-message {
- position: relative;
- top: -5px;
- margin-left: 25px;
- font-weight:normal;
- font-size: 12px;
- line-height: 20px;
- padding-left: 25px;
- padding-right: 25px;
- padding-top: 5px;
- padding-bottom: 4px;
- -moz-border-radius: 3px;
- -webkit-border-radius: 3px;
- border-radius: 3px;
-}
-
-.edit-flow-updated-message {
- background-color: #FFFFE0;
- border: 1px solid #E6DB55;
-}
-
-.edit-flow-error-message {
- background-color: #FFEBE8;
- border: 1px solid #CC0000;
-}
-
/* Admin Menu Icon */
#adminmenu .toplevel_page_ef-settings .wp-menu-image img {
width: 16px;
diff --git a/modules/settings/settings.php b/modules/settings/settings.php
index 133f52f36..ba2a0210e 100644
--- a/modules/settings/settings.php
+++ b/modules/settings/settings.php
@@ -1,377 +1,457 @@
module_url = $this->get_module_url( __FILE__ );
- $args = array(
- 'title' => __( 'Edit Flow', 'edit-flow' ),
- 'short_description' => __( 'Edit Flow redefines your WordPress publishing workflow.', 'edit-flow' ),
- 'extended_description' => __( 'Enable any of the features below to take control of your workflow. Custom statuses, email notifications, editorial comments, and more help you and your team save time so everyone can focus on what matters most: the content.', 'edit-flow' ),
- 'module_url' => $this->module_url,
- 'img_url' => $this->module_url . 'lib/eflogo_s128.png',
- 'slug' => 'settings',
- 'settings_slug' => 'ef-settings',
- 'default_options' => array(
- 'enabled' => 'on',
- ),
- 'configure_page_cb' => 'print_default_settings',
- 'autoload' => true,
- );
- $this->module = EditFlow()->register_module( 'settings', $args );
- }
-
- /**
- * Initialize the rest of the stuff in the class if the module is active
- */
- function init() {
-
- add_action( 'admin_init', array( $this, 'helper_settings_validate_and_save' ), 100 );
-
- add_action( 'admin_print_styles', array( $this, 'action_admin_print_styles' ) );
- add_action( 'admin_print_scripts', array( $this, 'action_admin_print_scripts' ) );
- add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
- add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
-
- add_action( 'wp_ajax_change_edit_flow_module_state', array( $this, 'ajax_change_edit_flow_module_state' ) );
-
- }
-
- /**
- * Add necessary things to the admin menu
- */
- function action_admin_menu() {
- global $edit_flow;
-
- $ef_logo = 'lib/eflogo_s32w.png';
-
- add_menu_page( $this->module->title, $this->module->title, 'manage_options', $this->module->settings_slug, array( $this, 'settings_page_controller' ), $this->module->module_url . $ef_logo ) ;
-
- foreach ( $edit_flow->modules as $mod_name => $mod_data ) {
- if ( isset( $mod_data->options->enabled ) && $mod_data->options->enabled == 'on'
- && $mod_data->configure_page_cb && $mod_name != $this->module->name )
- add_submenu_page( $this->module->settings_slug, $mod_data->title, $mod_data->title, 'manage_options', $mod_data->settings_slug, array( $this, 'settings_page_controller' ) ) ;
+ class EF_Settings extends EF_Module {
+
+ /**
+ * The module object.
+ *
+ * @var object
+ */
+ public $module;
+
+ /**
+ * Register the module with Edit Flow but don't do anything else.
+ */
+ public function __construct() {
+ // Register the module with Edit Flow.
+ $this->module_url = $this->get_module_url( __FILE__ );
+ $args = array(
+ 'title' => __( 'Edit Flow', 'edit-flow' ),
+ 'short_description' => __( 'Edit Flow redefines your WordPress publishing workflow.', 'edit-flow' ),
+ 'extended_description' => __( 'Enable any of the features below to take control of your workflow. Custom statuses, email notifications, editorial comments, and more help you and your team save time so everyone can focus on what matters most: the content.', 'edit-flow' ),
+ 'module_url' => $this->module_url,
+ 'img_url' => $this->module_url . 'lib/eflogo_s128.png',
+ 'slug' => 'settings',
+ 'settings_slug' => 'ef-settings',
+ 'default_options' => array(
+ 'enabled' => 'on',
+ ),
+ 'configure_page_cb' => 'print_default_settings',
+ 'autoload' => true,
+ );
+ $this->module = EditFlow()->register_module( 'settings', $args );
}
- }
-
- function action_admin_enqueue_scripts() {
-
- if ( $this->is_whitelisted_settings_view() )
- wp_enqueue_script( 'edit-flow-settings-js', $this->module_url . 'lib/settings.js', array( 'jquery' ), EDIT_FLOW_VERSION, true );
-
- }
-
- /**
- * Add settings styles to the settings page
- */
- function action_admin_print_styles() {
-
- if ( $this->is_whitelisted_settings_view() )
- wp_enqueue_style( 'edit_flow-settings-css', $this->module_url . 'lib/settings.css', false, EDIT_FLOW_VERSION );
-
-
- }
-
- /**
- * Extra data we need on the page for transitions, etc.
- *
- * @since 0.7
- */
- function action_admin_print_scripts() {
- ?>
+
+ /**
+ * Initialize the rest of the stuff in the class if the module is active.
+ */
+ public function init() {
+ add_action( 'admin_init', array( $this, 'helper_settings_validate_and_save' ), 100 );
+
+ add_action( 'admin_print_styles', array( $this, 'action_admin_print_styles' ) );
+ add_action( 'admin_print_scripts', array( $this, 'action_admin_print_scripts' ) );
+ add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
+ add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
+
+ add_action( 'wp_ajax_change_edit_flow_module_state', array( $this, 'ajax_change_edit_flow_module_state' ) );
+ }
+
+ /**
+ * Add necessary things to the admin menu.
+ */
+ public function action_admin_menu() {
+ global $edit_flow;
+
+ $ef_logo = 'lib/eflogo_s32w.png';
+
+ add_menu_page( $this->module->title, $this->module->title, 'manage_options', $this->module->settings_slug, array( $this, 'settings_page_controller' ), $this->module->module_url . $ef_logo );
+
+ // Add "Features" as the first submenu item (replaces the duplicate "Edit Flow" item).
+ add_submenu_page( $this->module->settings_slug, __( 'Features', 'edit-flow' ), __( 'Features', 'edit-flow' ), 'manage_options', $this->module->settings_slug, array( $this, 'settings_page_controller' ) );
+
+ foreach ( $edit_flow->modules as $mod_name => $mod_data ) {
+ if ( isset( $mod_data->options->enabled ) && 'on' == $mod_data->options->enabled
+ && $mod_data->configure_page_cb && $mod_name != $this->module->name ) {
+ add_submenu_page( $this->module->settings_slug, $mod_data->title, $mod_data->title, 'manage_options', $mod_data->settings_slug, array( $this, 'settings_page_controller' ) );
+ }
+ }
+ }
+
+ /**
+ * Enqueue scripts for the settings page.
+ */
+ public function action_admin_enqueue_scripts() {
+ if ( $this->is_whitelisted_settings_view() ) {
+ wp_enqueue_script( 'edit-flow-settings-js', $this->module_url . 'lib/settings.js', array( 'jquery' ), EDIT_FLOW_VERSION, true );
+ }
+ }
+
+ /**
+ * Add settings styles to the settings page.
+ */
+ public function action_admin_print_styles() {
+ if ( $this->is_whitelisted_settings_view() ) {
+ wp_enqueue_style( 'edit_flow-settings-css', $this->module_url . 'lib/settings.css', false, EDIT_FLOW_VERSION );
+ }
+ }
+
+ /**
+ * Extra data we need on the page for transitions, etc.
+ *
+ * @since 0.7
+ */
+ public function action_admin_print_scripts() {
+ ?>
- get_module_by( 'slug', $slug );
-
- if ( !$module )
- die('-1');
-
- if ( $module_action == 'enable' )
- $return = $edit_flow->update_module_option( $module->name, 'enabled', 'on' );
- else if ( $module_action == 'disable' )
- $return = $edit_flow->update_module_option( $module->name, 'enabled', 'off' );
-
- if ( $return )
- die('1');
- else
- die('-1');
- }
-
- /**
- * Handles all settings and configuration page requests. Required element for Edit Flow
- */
- function settings_page_controller() {
- global $edit_flow;
-
- $requested_module = $edit_flow->get_module_by( 'settings_slug', $_GET['page'] );
- if ( !$requested_module )
- wp_die( __( 'Not a registered Edit Flow module', 'edit-flow' ) );
- $configure_callback = $requested_module->configure_page_cb;
- $requested_module_name = $requested_module->name;
-
- // Don't show the settings page for the module if the module isn't activated
- if ( !$this->module_enabled( $requested_module_name ) ) {
- echo '
' . sprintf( __( 'Module not enabled. Please enable it from the Edit Flow settings page.', 'edit-flow' ), EDIT_FLOW_SETTINGS_PAGE ) . '
';
- return;
- }
-
- $this->print_default_header( $requested_module );
- $edit_flow->$requested_module_name->$configure_callback();
- $this->print_default_footer( $requested_module );
-
- }
-
- /**
- *
- */
- function print_default_header( $current_module ) {
-
- // If there's been a message, let's display it
- if ( isset( $_GET['message'] ) )
- $message = $_GET['message'];
- else if ( isset( $_REQUEST['message'] ) )
- $message = $_REQUEST['message'];
- else if ( isset( $_POST['message'] ) )
- $message = $_POST['message'];
- else
- $message = false;
- if ( $message && isset( $current_module->messages[$message] ) )
- $display_text = '
' . esc_html( $current_module->messages[$message] ) . '';
-
- // If there's been an error, let's display it
- if ( isset( $_GET['error'] ) )
- $error = $_GET['error'];
- else if ( isset( $_REQUEST['error'] ) )
- $error = $_REQUEST['error'];
- else if ( isset( $_POST['error'] ) )
- $error = $_POST['error'];
- else
- $error = false;
- if ( $error && isset( $current_module->messages[$error] ) )
- $display_text = '
' . esc_html( $current_module->messages[$error] ) . '';
-
- if ( $current_module->img_url )
- $page_icon = '
 . ')
';
- else
- $page_icon = '
';
- ?>
+ get_module_by( 'slug', $slug );
+
+ if ( ! $module ) {
+ wp_die( '-1' );
+ }
+
+ if ( 'enable' == $module_action ) {
+ $return = $edit_flow->update_module_option( $module->name, 'enabled', 'on' );
+ } elseif ( 'disable' == $module_action ) {
+ $return = $edit_flow->update_module_option( $module->name, 'enabled', 'off' );
+ }
+
+ if ( $return ) {
+ wp_die( '1' );
+ } else {
+ wp_die( '-1' );
+ }
+ }
+
+ /**
+ * Handles all settings and configuration page requests. Required element for Edit Flow.
+ *
+ * phpcs:disable WordPress.Security.NonceVerification.Recommended -- Rendering only, no data modification.
+ */
+ public function settings_page_controller() {
+ global $edit_flow;
+
+ $page_requested = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : 'settings';
+ // phpcs:enable WordPress.Security.NonceVerification
+ $requested_module = $edit_flow->get_module_by( 'settings_slug', $page_requested );
+ if ( ! $requested_module ) {
+ wp_die( esc_html__( 'Not a registered Edit Flow module', 'edit-flow' ) );
+ }
+
+ $configure_callback = $requested_module->configure_page_cb;
+ $requested_module_name = $requested_module->name;
+
+ // Don't show the settings page for the module if the module isn't activated.
+ if ( ! $this->module_enabled( $requested_module_name ) ) {
+ /* translators: 1: link to the settings page for Edit Flow */
+ echo '
' . wp_kses( sprintf( __( 'Module not enabled. Please enable it from the Edit Flow settings page.', 'edit-flow' ), esc_url( EDIT_FLOW_SETTINGS_PAGE ) ), 'a' ) . '
';
+ return;
+ }
+
+ $this->print_default_header( $requested_module );
+ $edit_flow->$requested_module_name->$configure_callback();
+ $this->print_default_footer( $requested_module );
+ }
+
+ /**
+ * Print the default header for the settings page.
+ *
+ * Nonce verification is not available here - it's just rendering. The actual save
+ * is done in helper_settings_validate_and_save and that's guarded well.
+ *
+ * @param object $current_module The current module being displayed.
+ *
+ * phpcs:disable WordPress.Security.NonceVerification
+ */
+ public function print_default_header( $current_module ) {
+ // Register admin notices for standard WordPress display.
+ if ( isset( $_GET['message'] ) ) {
+ $message = sanitize_key( $_GET['message'] );
+ } elseif ( isset( $_REQUEST['message'] ) ) {
+ $message = sanitize_key( $_REQUEST['message'] );
+ } elseif ( isset( $_POST['message'] ) ) {
+ $message = sanitize_key( $_POST['message'] );
+ } else {
+ $message = false;
+ }
+ if ( $message && isset( $current_module->messages[ $message ] ) ) {
+ add_settings_error(
+ 'edit-flow',
+ 'edit-flow-' . $message,
+ $current_module->messages[ $message ],
+ 'success'
+ );
+ }
+
+ // If there's been an error, register it as an admin notice.
+ if ( isset( $_GET['error'] ) ) {
+ $error = sanitize_key( $_GET['error'] );
+ } elseif ( isset( $_REQUEST['error'] ) ) {
+ $error = sanitize_key( $_REQUEST['error'] );
+ } elseif ( isset( $_POST['error'] ) ) {
+ $error = sanitize_key( $_POST['error'] );
+ } else {
+ $error = false;
+ }
+ if ( $error && isset( $current_module->messages[ $error ] ) ) {
+ add_settings_error(
+ 'edit-flow',
+ 'edit-flow-' . $error,
+ $current_module->messages[ $error ],
+ 'error'
+ );
+ }
+
+ // Build the page title.
+ if ( 'settings' !== $current_module->name ) {
+ $page_title = sprintf(
+ /* translators: %s: module title */
+ __( 'Edit Flow: %s', 'edit-flow' ),
+ $current_module->title
+ );
+ } else {
+ $page_title = __( 'Edit Flow: Features', 'edit-flow' );
+ }
+ ?>
- name != 'settings' ): ?>
-
-
: title; ?>
-
-
-
-
-
+
+
+
+ short_description || $current_module->extended_description ) : ?>
- short_description ): ?>
-
short_description; ?>
+ short_description ) : ?>
+
short_description ); ?>
+
+ extended_description ) : ?>
+
extended_description ); ?>
- extended_description ): ?>
-
extended_description; ?>
-
-
+
+
print_modules(); ?>
-
- slug == 'settings' ): ?>
-
-
-
- module->settings_slug, false ) ); ?>" method="post">
+ module->options_group_name ); ?>
+ module->options_group_name ); ?>
+
+
+
+
+
+ modules_count ) {
- echo '
' . __( 'There are no Edit Flow modules registered', 'edit-flow' ) . '
';
- } else {
+ /**
+ * Print the list of Edit Flow modules on the settings page.
+ */
+ public function print_modules() {
+ global $edit_flow;
- foreach ( $edit_flow->modules as $mod_name => $mod_data ) {
- if ( $mod_data->autoload )
- continue;
+ if ( ! $edit_flow->modules_count ) {
+ echo '
' . esc_html__( 'There are no Edit Flow modules registered', 'edit-flow' ) . '
';
+ } else {
+ foreach ( $edit_flow->modules as $mod_name => $mod_data ) {
+ if ( $mod_data->autoload ) {
+ continue;
+ }
- $classes = array(
- 'edit-flow-module',
- );
- if ( $mod_data->options->enabled == 'on' )
- $classes[] = 'module-enabled';
- elseif ( $mod_data->options->enabled == 'off' )
- $classes[] = 'module-disabled';
- if ( $mod_data->configure_page_cb )
- $classes[] = 'has-configure-link';
- echo '
';
- if ( $mod_data->img_url )
- echo '
 . ')
';
- echo '
';
- echo '
';
}
-
}
-
- }
-
- /**
- * Given a form field and a description, prints either the error associated with the field or the description.
- *
- * @since 0.7
- *
- * @param string $field The form field for which to check for an error
- * @param string $description Unlocalized string to display if there was no error with the given field
- */
- function helper_print_error_or_description( $field, $description ) {
- if ( isset( $_REQUEST['form-errors'][$field] ) ): ?>
+
+ /**
+ * Given a form field and a description, prints either the error associated with the field or the description.
+ *
+ * @since 0.7
+ *
+ * @param string $field The form field for which to check for an error.
+ * @param string $description Unlocalized string to display if there was no error with the given field.
+ */
+ public function helper_print_error_or_description( $field, $description ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just checking for error display, no data modification.
+ if ( isset( $_REQUEST['form-errors'][ $field ] ) ) :
+ ?>
-
+
- __( 'Posts' ),
- 'page' => __( 'Pages' ),
- );
- $custom_post_types = $this->get_supported_post_types_for_module();
- if ( count( $custom_post_types ) ) {
- foreach( $custom_post_types as $custom_post_type => $args ) {
- $all_post_types[$custom_post_type] = $args->label;
- }
+ $title ) {
- echo '
';
- // Leave a note to the admin as a reminder that add_post_type_support has been used somewhere in their code
- if ( post_type_supports( $post_type, $module->post_type_support ) )
- echo ' 
' . sprintf( __( 'Disabled because add_post_type_support( \'%1$s\', \'%2$s\' ) is included in a loaded file.', 'edit-flow' ), $post_type, $module->post_type_support ) . '';
- echo '
';
+ /**
+ * Generate an option field to turn post type support on/off for a given module.
+ *
+ * @since 0.7
+ *
+ * @param object $module Edit Flow module we're generating the option field for.
+ * @param array $args Optional. Additional arguments.
+ */
+ public function helper_option_custom_post_type( $module, $args = array() ) {
+
+ $all_post_types = array(
+ 'post' => __( 'Posts', 'edit-flow' ),
+ 'page' => __( 'Pages', 'edit-flow' ),
+ );
+ $custom_post_types = $this->get_supported_post_types_for_module();
+ if ( count( $custom_post_types ) ) {
+ foreach ( $custom_post_types as $custom_post_type => $args ) {
+ $all_post_types[ $custom_post_type ] = $args->label;
+ }
+ }
+
+ foreach ( $all_post_types as $post_type => $title ) {
+ echo '
';
+ // Leave a note to the admin as a reminder that add_post_type_support has been used somewhere in their code.
+ if ( post_type_supports( $post_type, $module->post_type_support ) ) {
+ /* translators: 1: post type, 2: post type support */
+ echo ' 
' . esc_html( sprintf( __( 'Disabled because add_post_type_support( \'%1$s\', \'%2$s\' ) is included in a loaded file.', 'edit-flow' ), $post_type, $module->post_type_support ) ) . '';
+ }
+ echo '
';
+ }
}
-
- }
-
- /**
- * Validation and sanitization on the settings field
- * This method is called automatically/ doesn't need to be registered anywhere
- *
- * @since 0.7
- */
- function helper_settings_validate_and_save() {
-
- if ( !isset( $_POST['action'], $_POST['_wpnonce'], $_POST['option_page'], $_POST['_wp_http_referer'], $_POST['edit_flow_module_name'], $_POST['submit'] ) || !is_admin() )
- return false;
-
- global $edit_flow;
- $module_name = sanitize_key( $_POST['edit_flow_module_name'] );
-
- if ( $_POST['action'] != 'update'
- || $_POST['option_page'] != $edit_flow->$module_name->module->options_group_name )
- return false;
-
- if ( !current_user_can( 'manage_options' ) || !wp_verify_nonce( $_POST['_wpnonce'], $edit_flow->$module_name->module->options_group_name . '-options' ) )
- wp_die( __( 'Cheatin’ uh?' ) );
-
- $new_options = ( isset( $_POST[$edit_flow->$module_name->module->options_group_name] ) ) ? $_POST[$edit_flow->$module_name->module->options_group_name] : array();
-
- // Only call the validation callback if it exists?
- if ( method_exists( $edit_flow->$module_name, 'settings_validate' ) )
- $new_options = $edit_flow->$module_name->settings_validate( $new_options );
-
- // Cast our object and save the data.
- $new_options = (object)array_merge( (array)$edit_flow->$module_name->module->options, $new_options );
- $edit_flow->update_all_module_options( $edit_flow->$module_name->module->name, $new_options );
-
- // Redirect back to the settings page that was submitted without any previous messages
- $goback = add_query_arg( 'message', 'settings-updated', remove_query_arg( array( 'message'), wp_get_referer() ) );
- wp_safe_redirect( $goback );
- exit;
+ /**
+ * Validation and sanitization on the settings field.
+ *
+ * This method is called automatically and doesn't need to be registered anywhere.
+ *
+ * @since 0.7
+ *
+ * @return false|void Returns false if validation fails, otherwise redirects.
+ */
+ public function helper_settings_validate_and_save() {
+
+ if ( ! isset( $_POST['action'], $_POST['_wpnonce'], $_POST['option_page'], $_POST['_wp_http_referer'], $_POST['edit_flow_module_name'], $_POST['submit'] ) || ! is_admin() ) {
+ return false;
+ }
+
+ global $edit_flow;
+ $module_name = sanitize_key( $_POST['edit_flow_module_name'] );
+
+ if ( 'update' != $_POST['action']
+ || $_POST['option_page'] != $edit_flow->$module_name->module->options_group_name ) {
+ return false;
+ }
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonces don't need sanitization, just verification.
+ if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( $_POST['_wpnonce'], $edit_flow->$module_name->module->options_group_name . '-options' ) ) {
+ wp_die( esc_html__( 'Cheatin’ uh?', 'edit-flow' ) );
+ }
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization is handled by each module's settings_validate method.
+ $new_options = ( isset( $_POST[ $edit_flow->$module_name->module->options_group_name ] ) ) ? $_POST[ $edit_flow->$module_name->module->options_group_name ] : array();
+
+ // Only call the validation callback if it exists.
+ if ( method_exists( $edit_flow->$module_name, 'settings_validate' ) ) {
+ $new_options = $edit_flow->$module_name->settings_validate( $new_options );
+ }
+
+ // Cast our object and save the data.
+ $new_options = (object) array_merge( (array) $edit_flow->$module_name->module->options, $new_options );
+ $edit_flow->update_all_module_options( $edit_flow->$module_name->module->name, $new_options );
+
+ // Redirect back to the settings page that was submitted without any previous messages.
+ $referer = wp_get_referer();
+ if ( ! $referer ) {
+ $referer = admin_url( 'admin.php?page=' . $edit_flow->$module_name->module->settings_slug );
+ }
+ $goback = add_query_arg( 'message', 'settings-updated', remove_query_arg( array( 'message' ), $referer ) );
+ wp_safe_redirect( $goback );
+ exit;
+ }
}
-
-}
}
diff --git a/modules/story-budget/lib/story-budget-print.css b/modules/story-budget/lib/story-budget-print.css
index c5451da84..66c960dfa 100644
--- a/modules/story-budget/lib/story-budget-print.css
+++ b/modules/story-budget/lib/story-budget-print.css
@@ -1,99 +1,235 @@
-/* Print styles for Edit Flow's Story Budget view */
-
-/* Hide the styles that we don't need */
-#ef-story-budget-tablenav,
-#adminmenu,
+/**
+ * Print styles for Edit Flow's Story Budget view.
+ *
+ * Creates a clean, readable printout of the Story Budget content.
+ *
+ * @package EditFlow
+ * @since 0.7
+ * @updated 0.10.1 - Refreshed for modern WordPress admin
+ */
+
+/* ==========================================================================
+ Hide UI elements not needed for print
+ ========================================================================== */
+
+/* WordPress admin chrome */
#wpadminbar,
#adminmenuback,
-#screen-meta-links,
-#wphead,
-#footer,
+#adminmenuwrap,
+#adminmenu,
+#wpfooter,
#screen-meta,
-#ef-story-budget-wrap .change-date-buttons,
-#ef-story-budget-wrap .time-range input,
-#ef-story-budget-wrap .module-icon,
-.row-actions {
- display: none;
+#screen-meta-links,
+.screen-reader-shortcut,
+.notice,
+.update-nag,
+#wpbody-content > .notice,
+#wpbody-content > .updated,
+#wpbody-content > .error {
+ display: none !important;
+}
+
+/* Story Budget navigation and filters */
+#ef-story-budget-tablenav,
+#ef-story-budget-date-form,
+#ef-story-budget-filters,
+#ef-story-budget-title .module-icon,
+.row-actions,
+.handlediv {
+ display: none !important;
+}
+
+/* ==========================================================================
+ Page setup
+ ========================================================================== */
+
+@page {
+ margin: 1.5cm;
+ size: auto;
}
-#ef-story-budget-wrap .time-range form {
- display: inline;
+html {
+ font-size: 12pt;
}
body {
- font: 12pt/16pt Georgia, Times, serif;
- padding: 10px;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+ font-size: 12pt;
+ line-height: 1.5;
+ color: #1e1e1e;
+ background: #fff !important;
+ margin: 0;
+ padding: 0;
}
-a {
- color: #000000;
+/* ==========================================================================
+ Layout adjustments
+ ========================================================================== */
+
+#wpcontent,
+#wpbody,
+#wpbody-content {
+ margin: 0 !important;
+ padding: 0 !important;
}
-#wpbody {
- margin-left: 0px;
- margin-top: -20px;
+#ef-story-budget-wrap {
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
}
-#wpcontent {
- margin-left: 1em;
- margin-right: 2em;
+#ef-story-budget-title h2 {
+ font-size: 18pt;
+ font-weight: 600;
+ margin: 0 0 12pt 0;
+ padding: 0;
+ border-bottom: 2pt solid #1e1e1e;
+ padding-bottom: 6pt;
}
-#wpbody #wpbody-content,
-#wpbody #wpbody-content .postbox-container,
-.postbox,
-.inside,
-.widefat,
-.meta-box-sortables {
- float: none !important; /* Hack to prevent Gecko from cutting off floated elements in print */
+/* ==========================================================================
+ Category cards (postboxes)
+ ========================================================================== */
+
+.metabox-holder,
+.postbox-container {
+ display: block !important;
+ width: 100% !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ float: none !important;
+ flex-wrap: nowrap !important;
}
-.icon32 {
- display: block;
- float: none;
+.postbox {
+ display: block !important;
+ width: 100% !important;
+ flex-basis: 100% !important;
+ margin: 0 0 12pt 0 !important;
+ padding: 0 !important;
+ border: 1pt solid #ddd !important;
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ page-break-inside: avoid;
+ break-inside: avoid;
+}
+
+.postbox h3.hndle,
+.postbox .hndle {
+ font-size: 14pt;
+ font-weight: 600;
+ margin: 0;
+ padding: 8pt 10pt;
+ background: #f6f7f7 !important;
+ border-bottom: 1pt solid #ddd;
+ cursor: default;
}
-.wrap h2 {
- font-style: normal;
+.postbox .inside {
+ margin: 0 !important;
+ padding: 0 !important;
}
-.meta-box-sortables .postbox {
- width: 100% !important; /* Important hack to override the inline styles applied to .postbox */
+/* Ensure collapsed boxes still print their content */
+.postbox.closed .inside {
+ display: block !important;
}
-.postbox,
-.widefat,
-.widefat td {
- border-color: #ffffff;
+/* ==========================================================================
+ Post tables
+ ========================================================================== */
+
+.widefat {
+ width: 100%;
+ border-collapse: collapse;
+ border: none;
+ margin: 0;
}
-.widefat td {
- padding-top: 10px !important;
+.widefat thead th {
+ font-size: 10pt;
+ font-weight: 600;
+ text-align: left;
+ padding: 6pt 10pt;
+ background: #f0f0f1 !important;
+ border-bottom: 1pt solid #ddd;
}
-.widefat td,
-.widefat th,
-.row-title {
- font-size: 13px !important;
- line-height: 17px;
+.widefat tbody td {
+ font-size: 10pt;
+ padding: 8pt 10pt;
+ border-bottom: 1pt solid #eee;
+ vertical-align: top;
}
-.widefat td p {
- color: #3d3d3d;
+.widefat tbody tr:last-child td {
+ border-bottom: none;
}
-.postbox {
- margin-bottom: 30px;
+.widefat .row-title {
+ font-size: 11pt;
+ font-weight: 600;
+ color: #1e1e1e;
}
-.postbox .hndle {
- background-image: none;
+.widefat .row-title a {
+ color: #1e1e1e;
+ text-decoration: none;
+}
+
+/* ==========================================================================
+ Post details and excerpts
+ ========================================================================== */
+
+.post-details {
+ display: block !important;
+}
+
+.post-details .post-excerpt {
+ font-size: 10pt;
+ font-style: italic;
+ color: #50575e;
+ margin: 4pt 0 0 0;
+}
+
+/* ==========================================================================
+ Links and colors
+ ========================================================================== */
+
+a {
+ color: #1e1e1e;
+ text-decoration: none;
+}
+
+/* Show URLs after links for reference */
+.widefat .row-title a::after {
+ content: none; /* Don't show URLs - they clutter the printout */
+}
+
+/* ==========================================================================
+ Empty state messages
+ ========================================================================== */
+
+.postbox .inside .message {
+ font-size: 10pt;
+ color: #50575e;
+ padding: 8pt 10pt;
+ font-style: italic;
+}
+
+/* ==========================================================================
+ Page breaks
+ ========================================================================== */
+
+h2 {
+ page-break-after: avoid;
+}
+
+.postbox h3.hndle {
+ page-break-after: avoid;
}
-.meta-box-sortables .postbox h3 {
- border-bottom: 1px solid #e1e1e1;
- font-family: Helvetica, Arial, sans-serif;
- font-size: 20px;
- font-weight: normal;
- text-shadow: none;
+.widefat tr {
+ page-break-inside: avoid;
}
\ No newline at end of file
diff --git a/modules/story-budget/lib/story-budget.css b/modules/story-budget/lib/story-budget.css
index 2ae5c8a87..67dc17635 100644
--- a/modules/story-budget/lib/story-budget.css
+++ b/modules/story-budget/lib/story-budget.css
@@ -8,38 +8,52 @@
max-width: 300px;
}
-#ef-story-budget-title h2 span {
- color: #999999;
+/* Date form comes first, then filters */
+#ef-story-budget-date-form {
+ display: inline-block;
+ margin-right: 10px;
+ padding-right: 10px;
+ border-right: 1px solid #c3c4c7;
+ vertical-align: middle;
}
-#ef-story-budget-title h2 form {
- display: inline;
- position: relative;
+#ef-story-budget-filters {
+ display: inline-block;
+ vertical-align: middle;
}
-#ef-story-budget-title h2 form input {
- display:none;
+#ef-story-budget-date-form input[type="text"] {
+ width: auto;
+ vertical-align: middle;
}
-#ef-story-budget-title h2 a.change-date {
- display:inline-block;
+#ef-story-budget-date-form #ef-story-budget-start-date {
+ width: 11em;
}
-#ef-story-budget-title h2 a.change-date,
-#ef-story-budget-title h2 a.change-date-cancel {
- font-size: 14px;
+#ef-story-budget-date-form #ef-story-budget-number-days {
+ width: 4em;
+ padding-right: 0;
+ vertical-align: middle;
}
-.postbox {
- display: block;
- margin: 0 auto 10px;
- justify-content: space-between;
+#ef-story-budget-date-form button {
+ vertical-align: middle;
}
+/* Card layout - gaps only between cards, not at edges */
.postbox-container {
padding-right: 0;
display: flex;
flex-flow: row wrap;
+ align-items: flex-start;
+ gap: 10px;
+}
+
+.postbox {
+ display: block;
+ margin: 0;
+ box-sizing: border-box;
}
.postbox .inside {
@@ -73,4 +87,81 @@
#start_date, #end_date {
width: 100px;
+}
+
+/* Post excerpts - hidden by default, shown via screen option */
+.post-details {
+ display: none;
+}
+
+.show-excerpts .post-details {
+ display: block;
+}
+
+.post-details .post-excerpt {
+ margin: 0.5em 0;
+ color: #666;
+ font-style: italic;
+}
+
+/* Hide empty categories when option is enabled */
+.hide-empty-terms .postbox:not(.postbox-has-posts) {
+ display: none;
+}
+
+/* Ensure consistent card width regardless of content */
+.postbox .inside .message {
+ min-height: 1em;
+}
+
+/* Override WordPress postbox drag cursor - cards are not draggable */
+#ef-story-budget-wrap .postbox h3.hndle {
+ cursor: default;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+/* Toggle button styling */
+#ef-story-budget-wrap .postbox .handlediv {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ margin: -8px -10px -8px 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ cursor: pointer;
+ border-radius: 0;
+}
+
+#ef-story-budget-wrap .postbox .handlediv:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+
+#ef-story-budget-wrap .postbox .handlediv:focus {
+ outline: 1px dotted #1e8cbe;
+ outline-offset: -1px;
+}
+
+#ef-story-budget-wrap .postbox .handlediv .toggle-indicator {
+ display: block;
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 6px solid #50575e;
+ transition: transform 0.15s ease-in-out;
+}
+
+#ef-story-budget-wrap .postbox.closed .handlediv .toggle-indicator {
+ transform: rotate(-90deg);
+}
+
+/* Collapsed postbox styling */
+#ef-story-budget-wrap .postbox.closed {
+ padding-bottom: 0;
+ border-bottom: 1px solid #c3c4c7;
}
\ No newline at end of file
diff --git a/modules/story-budget/lib/story-budget.js b/modules/story-budget/lib/story-budget.js
index d6a2c8103..7b520eaab 100644
--- a/modules/story-budget/lib/story-budget.js
+++ b/modules/story-budget/lib/story-budget.js
@@ -1,19 +1,18 @@
// Story Budget specific JS, assumes that ef_date.js has already been included
jQuery( document ).ready( function ( $ ) {
- // Make print link open up print dialog
- $( '#print_link' ).on( 'click', function () {
- window.print();
- return false;
- } );
+ // Hide/show a single category section when clicking the toggle button
+ $( 'button.handlediv' ).on( 'click', function () {
+ const $postbox = $( this ).closest( '.postbox' );
+ const $inside = $postbox.children( 'div.inside' );
+ const isExpanded = $( this ).attr( 'aria-expanded' ) === 'true';
- // Hide a single section when directed
- $( 'h3.hndle,div.handlediv' ).on( 'click', function () {
- $( this ).parent().children( 'div.inside' ).toggle();
+ $inside.toggle();
+ $postbox.toggleClass( 'closed', isExpanded );
+ $( this ).attr( 'aria-expanded', ! isExpanded );
} );
// Change number of columns when choosing a new number from Screen Options
-
const columnsSwitch = $( 'input[name=ef_story_budget_screen_columns]' );
columnsSwitch.on( 'click', function () {
const numColumns = parseInt( $( this ).val() );
@@ -28,19 +27,21 @@ jQuery( document ).ready( function ( $ ) {
.addClass( classPrefix + numColumns );
} );
- $( 'h2 a.change-date' ).on( 'click', function () {
- $( this ).hide();
- $( 'h2 form .form-value' ).hide();
- $( 'h2 form input' ).show();
- $( 'h2 form a.change-date-cancel' ).show();
- return false;
+ // Toggle excerpts visibility instantly from Screen Options
+ $( 'input[name=ef_story_budget_show_excerpts]' ).on( 'change', function () {
+ if ( $( this ).is( ':checked' ) ) {
+ $( '#ef-story-budget-wrap' ).addClass( 'show-excerpts' );
+ } else {
+ $( '#ef-story-budget-wrap' ).removeClass( 'show-excerpts' );
+ }
} );
- $( 'h2 form a.change-date-cancel' ).on( 'click', function () {
- $( this ).hide();
- $( 'h2 form .form-value' ).show();
- $( 'h2 form input' ).hide();
- $( 'h2 form a.change-date' ).show();
- return false;
+ // Toggle empty categories visibility instantly from Screen Options
+ $( 'input[name=ef_story_budget_hide_empty_terms]' ).on( 'change', function () {
+ if ( $( this ).is( ':checked' ) ) {
+ $( '#ef-story-budget-wrap' ).addClass( 'hide-empty-terms' );
+ } else {
+ $( '#ef-story-budget-wrap' ).removeClass( 'hide-empty-terms' );
+ }
} );
} );
diff --git a/modules/story-budget/story-budget.php b/modules/story-budget/story-budget.php
index ee524097f..a5f225b3a 100644
--- a/modules/story-budget/story-budget.php
+++ b/modules/story-budget/story-budget.php
@@ -1,126 +1,190 @@
module_url = $this->get_module_url( __FILE__ );
- // Register the module with Edit Flow
- $args = array(
- 'title' => __( 'Story Budget', 'edit-flow' ),
- 'short_description' => sprintf( __( 'View the status of all your content
at a glance.', 'edit-flow' ), admin_url( 'index.php?page=story-budget' ) ),
+ // 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,
- 'img_url' => $this->module_url . 'lib/story_budget_s128.png',
- 'slug' => 'story-budget',
- 'default_options' => array(
+ 'module_url' => $this->module_url,
+ 'img_url' => $this->module_url . 'lib/story_budget_s128.png',
+ 'slug' => 'story-budget',
+ 'default_options' => array(
'enabled' => 'on',
),
- 'configure_page_cb' => false,
- 'autoload' => false,
+ 'configure_page_cb' => false,
+ 'autoload' => false,
);
$this->module = EditFlow()->register_module( 'story_budget', $args );
-
}
/**
- * Initialize the rest of the stuff in the class if the module is active
+ * 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 );
+ $this->num_columns = $this->get_num_columns();
+ $this->show_excerpts = $this->get_show_excerpts();
+ $this->hide_empty_terms = $this->get_hide_empty_terms();
+ $this->max_num_columns = apply_filters( 'ef_story_budget_max_num_columns', 3 );
- // Filter to allow users to pick a taxonomy other than 'category' for sorting their posts
+ // Filter to allow users to pick a taxonomy other than 'category' for sorting their posts.
$this->taxonomy_used = apply_filters( 'ef_story_budget_taxonomy_used', $this->taxonomy_used );
add_action( 'admin_init', array( $this, 'handle_form_date_range_change' ) );
+ add_action( 'admin_init', array( $this, 'handle_filter_reset' ) );
add_action( 'admin_init', array( $this, 'add_screen_options_panel' ) );
// Register the columns of data appearing on every term. This is hooked into admin_init
- // so other Edit Flow modules can register their filters if needed
+ // so other Edit Flow modules can register their filters if needed.
add_action( 'admin_init', array( $this, 'register_term_columns' ) );
add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
- // Load necessary scripts and stylesheets
+ // 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' ) );
-
}
/**
- * Give users the appropriate permissions to view the story budget the first time the module is loaded
+ * Give users the appropriate permissions to view the story budget the first time the module is loaded.
*
* @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 );
}
}
/**
- * Upgrade our data in case we need to
+ * Upgrade our data in case we need to.
*
* @since 0.7
+ *
+ * @param string $previous_version Previous plugin version.
*/
- function upgrade( $previous_version ) {
+ public function upgrade( $previous_version ) {
global $edit_flow;
- // Upgrade path to v0.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' ) )
- $enabled = 'on';
- else
- $enabled = 'off';
+ // Upgrade path to v0.7.
+ if ( version_compare( $previous_version, '0.7', '<' ) ) {
+ // Migrate whether the story budget was enabled or not and clean up old option.
+ $enabled = get_option( 'edit_flow_story_budget_enabled' ) ? 'on' : '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
+ // 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 +192,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 +201,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 +218,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,55 +235,106 @@ function action_enqueue_admin_styles() {
*
* @since 0.7
*/
- function register_term_columns() {
+ public function register_term_columns() {
$term_columns = array(
- 'title' => __( 'Title', 'edit-flow' ),
- 'status' => __( 'Status', 'edit-flow' ),
- 'author' => __( 'Author', 'edit-flow' ),
- 'post_date' => __( 'Post Date', 'edit-flow' ),
+ 'title' => __( 'Title', 'edit-flow' ),
+ 'status' => __( 'Status', 'edit-flow' ),
+ 'author' => __( 'Author', 'edit-flow' ),
+ 'post_date' => __( 'Post Date', 'edit-flow' ),
'post_modified' => __( 'Last Modified', 'edit-flow' ),
);
- $term_columns = apply_filters( 'ef_story_budget_term_columns', $term_columns );
+ $term_columns = apply_filters( 'ef_story_budget_term_columns', $term_columns );
$this->term_columns = $term_columns;
}
/**
- * Handle a form submission to change the user's date range on the budget
+ * Handle a request to reset filters.
*
- * @since 0.7
+ * @since 0.10
+ *
+ * phpcs:disable WordPress.Security.NonceVerification.Recommended -- Resetting filters is a safe operation.
*/
- 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'] ) ) {
+ public function handle_filter_reset() {
+ if ( ! isset( $_GET['page'] ) || 'story-budget' !== $_GET['page'] ) {
return;
}
- if ( !wp_verify_nonce( $_POST['nonce'], 'change-date' ) )
- wp_die( $this->module->messages['nonce-failed'] );
+ if ( ! isset( $_GET['reset-filters'] ) || '1' !== $_GET['reset-filters'] ) {
+ return;
+ }
$current_user = wp_get_current_user();
- $new_filters = array (
- 'start_date' => $_POST['ef-story-budget-start-date_hidden'],
+
+ // Clear filter values from user meta, preserving date settings.
+ $existing_filters = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true );
+
+ $user_filters = array(
+ 'post_status' => '',
+ 'cat' => '',
+ 'author' => '',
+ 'start_date' => isset( $existing_filters['start_date'] ) ? $existing_filters['start_date'] : null,
+ 'number_days' => isset( $existing_filters['number_days'] ) ? $existing_filters['number_days'] : null,
+ );
+
+ $this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $user_filters );
+
+ // Redirect to clean URL without the reset-filters param.
+ wp_safe_redirect( menu_page_url( $this->module->slug, false ) );
+ exit;
+ }
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
+
+ /**
+ * Handle a form submission to change the user's date range on the budget.
+ *
+ * @since 0.7
+ */
+ public function handle_form_date_range_change() {
+ if ( ! isset( $_POST['ef-story-budget-range-submit'] ) || ! isset( $_POST['ef-story-budget-number-days'] ) ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonces don't need sanitization, just verification.
+ 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();
+ $existing_filters = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true );
+
+ // Use form values, but preserve start_date if hidden field is empty.
+ $start_date = ! empty( $_POST['ef-story-budget-start-date_hidden'] )
+ ? sanitize_text_field( $_POST['ef-story-budget-start-date_hidden'] )
+ : ( isset( $existing_filters['start_date'] ) ? $existing_filters['start_date'] : current_time( 'Y-m-d' ) );
+
+ $new_filters = array(
+ 'start_date' => $start_date,
'number_days' => (int) $_POST['ef-story-budget-number-days'],
);
+
$user_filters = $this->update_user_filters_from_form_date_range_change( $current_user, $new_filters );
$this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $user_filters );
- wp_redirect( menu_page_url( $this->module->slug, false ) );
+
+ wp_safe_redirect( menu_page_url( $this->module->slug, false ) );
exit;
}
/**
- * Handles updating the users
+ * Handles updating the users.
+ *
+ * @param WP_User $current_user The current user.
+ * @param array $new_filters The new filters to apply.
+ * @return array The updated filters.
*/
public function update_user_filters_from_form_date_range_change( $current_user, $new_filters ) {
$existing_filters = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true );
- // Default start date value
+ // Default start date value.
if ( isset( $new_filters['start_date'] ) ) {
- // Validate that it's a legitimate date
+ // Validate that it's a legitimate date.
$valid_date = DateTime::createFromFormat( 'Y-m-d', $new_filters['start_date'] );
if ( false === $valid_date ) {
@@ -226,17 +343,14 @@ public function update_user_filters_from_form_date_range_change( $current_user,
$start_date = $valid_date->format( 'Y-m-d' );
}
- // Set the start_date filter (to new value or default)
+ // Set the start_date filter (to new value or default).
$existing_filters['start_date'] = $start_date;
}
if ( isset( $new_filters['number_days'] ) ) {
$new_filters['number_days'] = intval( $new_filters['number_days'] );
- if ( $new_filters['number_days'] <= 1 ) {
- $existing_filters['number_days'] = 1;
- } else {
- $existing_filters['number_days'] = $new_filters['number_days'];
- }
+ // Enforce min 1 and max 30.
+ $existing_filters['number_days'] = max( 1, min( 30, $new_filters['number_days'] ) );
}
return $existing_filters;
@@ -245,12 +359,12 @@ 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();
+ $current_user = wp_get_current_user();
$this->num_columns = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_columns', true );
- // If usermeta didn't have a value already, use a default value and insert into DB
+ // If usermeta didn't have a value already, use a default value and insert into DB.
if ( empty( $this->num_columns ) ) {
$this->num_columns = self::default_num_columns;
$this->save_column_prefs( array( self::usermeta_key_prefix . 'screen_columns' => $this->num_columns ) );
@@ -259,37 +373,78 @@ function get_num_columns() {
return $this->num_columns;
}
+ /**
+ * Get the user's preference for showing excerpts.
+ *
+ * @return bool Whether to show excerpts.
+ */
+ public function get_show_excerpts() {
+ $current_user = wp_get_current_user();
+ $show = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'show_excerpts', true );
+ return (bool) $show;
+ }
+
+ /**
+ * Get the user's preference for hiding empty categories.
+ *
+ * @return bool Whether to hide empty categories.
+ */
+ public function get_hide_empty_terms() {
+ $current_user = wp_get_current_user();
+ $hide = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'hide_empty_terms', true );
+ return (bool) $hide;
+ }
+
/**
* Add module options to the screen panel
*
* @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_val .= '
';
+ $return_val .= "
\n";
+
+ $return_val .= '
';
+ $return_val .= "
\n";
+
return $return_val;
}
/**
- * Save the current user's preference for number of columns.
+ * Save the current user's screen preferences.
+ *
+ * @param array $posted_fields The posted fields.
*/
- function save_column_prefs( $posted_fields ) {
+ public function save_column_prefs( $posted_fields ) {
+ $current_user = wp_get_current_user();
- $key = self::usermeta_key_prefix . 'screen_columns';
- $this->num_columns = (int) $posted_fields[ $key ];
+ // Save columns preference.
+ $columns_key = self::usermeta_key_prefix . 'screen_columns';
+ $this->num_columns = isset( $posted_fields[ $columns_key ] ) ? (int) $posted_fields[ $columns_key ] : self::default_num_columns;
+ $this->update_user_meta( $current_user->ID, $columns_key, $this->num_columns );
- $current_user = wp_get_current_user();
- $this->update_user_meta( $current_user->ID, $key, $this->num_columns );
+ // Save excerpts preference.
+ $excerpts_key = self::usermeta_key_prefix . 'show_excerpts';
+ $this->show_excerpts = ! empty( $posted_fields[ $excerpts_key ] );
+ $this->update_user_meta( $current_user->ID, $excerpts_key, $this->show_excerpts ? 1 : 0 );
+
+ // Save hide empty terms preference.
+ $hide_empty_key = self::usermeta_key_prefix . 'hide_empty_terms';
+ $this->hide_empty_terms = ! empty( $posted_fields[ $hide_empty_key ] );
+ $this->update_user_meta( $current_user->ID, $hide_empty_key, $this->hide_empty_terms ? 1 : 0 );
}
/**
@@ -297,53 +452,66 @@ 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
+ // 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] ) ) {
- $terms = array();
- $terms[] = get_term( $this->user_filters[$this->taxonomy_used], $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 );
} else {
- // Get all of the terms from the taxonomy, regardless whether there are published posts
- $args = array(
- 'orderby' => 'name',
- 'order' => 'asc',
+ // Get all of the terms from the taxonomy, regardless whether there are published posts.
+ $terms = get_terms( array(
+ 'taxonomy' => $this->taxonomy_used,
+ 'orderby' => 'name',
+ 'order' => 'asc',
'hide_empty' => 0,
- 'parent' => 0,
- );
- $terms = get_terms( $this->taxonomy_used, $args );
+ 'parent' => 0,
+ ));
}
- $this->terms = apply_filters( 'ef_story_budget_filter_terms', $terms ); // allow for reordering or any other filtering of terms
+ // Allow for reordering or any other filtering of terms.
+ $this->terms = apply_filters( 'ef_story_budget_filter_terms', $terms );
+ $wrap_classes = 'wrap';
+ if ( $this->show_excerpts ) {
+ $wrap_classes .= ' show-excerpts';
+ }
+ if ( $this->hide_empty_terms ) {
+ $wrap_classes .= ' hide-empty-terms';
+ }
?>
-
+
module->img_url ) . '" class="module-icon icon32" />'; ?>
-
story_budget_time_range(); ?>
+
print_messages(); ?>
table_navigation(); ?>
num_columns ) . '">';
- foreach( (array) $this->terms as $term ) {
- $this->print_term( $term );
- }
+ foreach ( (array) $this->terms as $term ) {
+ $this->print_term( $term );
+ }
echo '
';
?>
@@ -355,52 +523,52 @@ function story_budget() {
*
* @since 0.7
*/
- function story_budget_time_range() {
+ public function story_budget_time_range() {
?>
-
null,
- 'author' => null,
+ 'post_status' => null,
+ 'author' => null,
'posts_per_page' => apply_filters( 'ef_story_budget_max_query', 200 ),
);
- $args = array_merge( $defaults, $args );
+ $args = array_merge( $defaults, $args );
- // Filter to the term and any children if it's hierarchical
+ // Filter to the term and any children if it's hierarchical.
$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 ) );
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Required for category/term filtering.
$args['tax_query'] = array(
array(
'taxonomy' => $this->taxonomy_used,
- 'field' => 'id',
- 'terms' => $arg_terms,
+ 'field' => 'id',
+ 'terms' => $arg_terms,
'operator' => 'IN',
),
);
@@ -408,7 +576,7 @@ function get_posts_for_term( $term, $args = null ) {
// Unpublished as a status is just an array of everything but 'publish'.
if ( 'unpublish' == $args['post_status'] ) {
$args['post_status'] = '';
- $post_stati = wp_filter_object_list( $this->get_budget_post_stati(), array( 'name' => 'publish' ), 'not' );
+ $post_stati = wp_filter_object_list( $this->get_budget_post_stati(), array( 'name' => 'publish' ), 'not' );
if ( ! apply_filters( 'ef_show_scheduled_as_unpublished', false ) ) {
$post_stati = wp_filter_object_list( $post_stati, array( 'name' => 'future' ), 'not' );
@@ -417,20 +585,22 @@ function get_posts_for_term( $term, $args = null ) {
$args['post_status'] .= implode( ',', wp_list_pluck( $post_stati, 'name' ) );
}
- // Filter by post_author if it's set
- if ( $args['author'] === '0' ) unset( $args['author'] );
+ // Filter by post_author if it's set.
+ 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 );
+ $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' => date( 'Y-m-d', $beginning_date ),
+ 'before' => date( 'Y-m-d', $ending_date ),
'inclusive' => true,
);
- // Filter for an end user to implement any of their own query args
+ // Filter for an end user to implement any of their own query args.
$args = apply_filters( 'ef_story_budget_posts_query_args', $args );
$term_posts_query_results = new WP_Query( $args );
@@ -451,23 +621,34 @@ 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 ) )
- // Don't display the message for $no_matching_posts
+ if ( ! empty( $posts ) ) {
+ // Don't display the message for $no_matching_posts.
$this->no_matching_posts = false;
+ }
- ?>
-
-
-
name ); ?>
+ ?>
+
+
+ name ); ?>
+
+
-
+
- term_columns as $key => $name ): ?>
+ term_columns as $key => $name ) : ?>
|
@@ -475,17 +656,18 @@ function print_term( $term ) {
print_post( $post, $term );
+ foreach ( $posts as $post ) {
+ $this->print_post( $post, $term );
+ }
?>
-
+
-
- term_columns as $key => $name ) {
- echo '| ';
+ term_columns as $key => $name ) {
+ $class = ( 'title' === $key ) ? ' class="post-title"' : '';
+ 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;
- break;
case 'author':
$post_author = get_userdata( $post->post_author );
return $post_author->display_name;
- break;
case 'post_date':
- $output = get_the_time( get_option( 'date_format' ), $post->ID ) . '
';
- $output .= get_the_time( get_option( 'time_format' ), $post->ID );
+ $output = get_the_time( get_option( 'date_format' ), $post->ID );
+ // Only show time if it's not midnight (indicating a specific time was set).
+ $post_time = get_the_time( 'H:i', $post->ID );
+ if ( '00:00' !== $post_time ) {
+ $output .= '
' . get_the_time( get_option( 'time_format' ), $post->ID );
+ }
return $output;
- break;
case 'post_modified':
+ // translators: %s is a human-readable time difference.
+ // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested, WordPress.WP.I18n.MissingTranslatorsComment -- Used for relative time display; translator comment is above.
return sprintf( esc_html__( '%s ago', 'edit-flow' ), human_time_diff( get_the_time( 'U', $post->ID ), current_time( 'timestamp' ) ) );
- break;
default:
break;
}
-
}
/**
- * Prepare the data for the title term column
+ * Prepare the data for the title term column.
*
* @since 0.7
+ *
+ * @param object $post The post object.
+ * @param object $parent_term The parent term for the column.
+ * @return string The HTML output for the title column.
*/
- 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 )
+ $can_edit_post = current_user_can( $post_type_object->cap->edit_post, $post->ID );
+ if ( $can_edit_post ) {
$output = '
' . esc_html( $post_title ) . '';
- else
+ } else {
$output = '
' . esc_html( $post_title ) . '';
+ }
+
+ // Post excerpt/details (toggleable).
+ $output .= '
';
+ if ( current_user_can( 'read_post', $post->ID ) ) {
+ if ( post_password_required( $post ) ) {
+ $output .= '
' . esc_html__( 'There is no excerpt because this is a protected post.', 'edit-flow' ) . '
';
+ } elseif ( ! empty( $post->post_excerpt ) ) {
+ $output .= '
' . esc_html( wp_strip_all_tags( $post->post_excerpt ) ) . '
';
+ } else {
+ $excerpt_length = apply_filters( 'ef_story_budget_excerpt_length', 20 );
+ $excerpt_more = apply_filters( 'excerpt_more', ' […]' );
+ $output .= '
' . wp_trim_words( wp_strip_all_tags( $post->post_content ), $excerpt_length, $excerpt_more ) . '
';
+ }
+ }
+ $output .= '
';
- // Edit or Trash or View
- $output .= '
';
+ // 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' ) ) )
+ // 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' ) ) ) {
+ // translators: %s is the post title.
$item_actions['view'] = '
' . __( 'View', 'edit-flow' ) . '';
- else if ( $can_edit_post )
+ } elseif ( $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 ) ) {
$output .= '
';
- $html = '';
+ $html = '';
foreach ( $item_actions as $class => $item_action ) {
$html .= '
' . $item_action . ' | ';
}
@@ -597,106 +809,105 @@ function term_column_title( $post, $parent_term ) {
}
/**
- * Print any messages that should appear based on the action performed
+ * Print any messages that should appear based on the action performed.
*/
- function print_messages() {
- ?>
-
-
+
';
- // Following mostly stolen from edit.php
+ // 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( '%d item moved to the trash.', '%d items moved to the trash.', (int) $_GET['trashed'], 'edit-flow' ) ), esc_html( number_format_i18n( (int) $_GET['trashed'] ) ) );
+ $ids = isset( $_GET['ids'] ) ? sanitize_text_field( wp_unslash( $_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( '%d item restored from the Trash.', '%d items restored from the Trash.', (int) $_GET['untrashed'], 'edit-flow' ) ), esc_html( number_format_i18n( (int) $_GET['untrashed'] ) ) );
+ unset( $_GET['undeleted'] );
}
echo '
';
}
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
+ // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
/**
* Print the table navigation and filter controls, using the current user's filters if any are set.
*/
- function table_navigation() {
- ?>
+ public function table_navigation() {
+ $has_active_filter = ! empty( $this->user_filters['post_status'] )
+ || ! empty( $this->user_filters['cat'] )
+ || ! empty( $this->user_filters['author'] );
+ ?>
- $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();
$current_user_filters = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true );
- // If any of the $_GET vars are missing, then use the current user filter
+ // 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'] )
+ if ( ! $user_filters['start_date'] ) {
$user_filters['start_date'] = date( '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,88 +919,116 @@ 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 usermeta didn't have filters already, insert defaults into DB.
+ if ( empty( $user_filters ) ) {
$user_filters = $this->update_user_filters();
+ }
return $user_filters;
}
/**
+ * Get a sanitized parameter from the $_GET superglobal.
*
- * @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
+ * @param string $param The parameter to look for in $_GET.
+ * @return null|string Null if the parameter is not set, empty string if empty,
+ * or a sanitized version of the parameter if set and not empty.
*/
- function filter_get_param( $param ) {
+ public function filter_get_param( $param ) {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only filter retrieval.
// 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] ) ) {
+ } elseif ( empty( $_GET[ $param ] ) ) {
return '';
}
- return sanitize_key( $_GET[$param] );
+ return sanitize_key( $_GET[ $param ] );
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
}
- function story_budget_filters() {
+ /**
+ * Get the available filter names for the story budget.
+ *
+ * @return array The filter names.
+ */
+ 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';
+ $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 ) {
+ /**
+ * Output the filter options for the story budget.
+ *
+ * @param string $select_id The ID of the select element.
+ * @param string $select_name The name of the select element.
+ * @param array $filters The current filter values.
+ */
+ public function story_budget_filter_options( $select_id, $select_name, $filters ) {
+ switch ( $select_id ) {
case 'post_status':
- $post_stati = $this->get_budget_post_stati();
- ?>
-