classes/class-alerts.php
<?php
/**
* Alerts feature class.
*
* @package WP_Stream
*/
namespace WP_Stream;
/**
* Class Alerts
*
* @package WP_Stream
*/
class Alerts {
/**
* Alerts post type slug
*/
const POST_TYPE = 'wp_stream_alerts';
/**
* Triggered Alerts meta key for Records
*/
const ALERTS_TRIGGERED_META_KEY = 'wp_stream_alerts_triggered';
/**
* Capability required to access alerts.
*/
const CAPABILITY = 'manage_options';
/**
* Holds Instance of plugin object
*
* @var Plugin
*/
public $plugin;
/**
* Post meta prefix
*
* @var string
*/
public $meta_prefix = 'wp_stream';
/**
* Alert Types
*
* @var array
*/
public $alert_types = array();
/**
* Alert Triggers
*
* @var array
*/
public $alert_triggers = array();
/**
* Class constructor.
*
* @param Plugin $plugin Instance of plugin object.
*/
public function __construct( $plugin ) {
$this->plugin = $plugin;
// Register custom post type.
add_action( 'init', array( $this, 'register_post_type' ) );
// Add custom post type to menu.
add_action( 'wp_stream_admin_menu', array( $this, 'register_menu' ) );
// Add scripts to post screens.
add_action(
'admin_enqueue_scripts',
array(
$this,
'register_scripts',
)
);
add_action(
'network_admin_menu',
array(
$this,
'change_menu_link_url',
),
99
);
add_filter(
'wp_stream_record_inserted',
array(
$this,
'check_records',
),
10,
2
);
add_action(
'wp_ajax_load_alerts_settings',
array(
$this,
'load_alerts_settings',
)
);
add_action( 'wp_ajax_get_actions', array( $this, 'get_actions' ) );
add_action(
'wp_ajax_save_new_alert',
array(
$this,
'save_new_alert',
)
);
add_action(
'wp_ajax_get_new_alert_triggers_notifications',
array(
$this,
'get_new_alert_triggers_notifications',
)
);
$this->load_alert_types();
$this->load_alert_triggers();
add_filter(
'wp_stream_action_links_posts',
array(
$this,
'change_alert_action_links',
),
11,
2
);
}
/**
* Load alert_type classes
*
* @return void
*/
public function load_alert_types() {
$alert_types = array(
'none',
'highlight',
'email',
'ifttt',
'slack',
);
$classes = array();
foreach ( $alert_types as $alert_type ) {
$file_location = $this->plugin->locations['dir'] . '/alerts/class-alert-type-' . $alert_type . '.php';
if ( file_exists( $file_location ) ) {
include_once $file_location;
$class_name = sprintf( '\WP_Stream\Alert_Type_%s', str_replace( '-', '_', $alert_type ) );
if ( ! class_exists( $class_name ) ) {
continue;
}
$class = new $class_name( $this->plugin );
if ( ! property_exists( $class, 'slug' ) ) {
continue;
}
$classes[ $class->slug ] = $class;
}
}
/**
* Allows for adding additional alert_types via classes that extend Notifier.
*
* @param array $classes An array of Notifier objects. In the format alert_type_slug => Notifier_Class()
*/
$this->alert_types = apply_filters( 'wp_stream_alert_types', $classes );
// Ensure that all alert_types extend Notifier.
foreach ( $this->alert_types as $key => $alert_type ) {
if ( ! $this->is_valid_alert_type( $alert_type ) ) {
unset( $this->alert_types[ $key ] );
}
}
}
/**
* Load alert_type classes
*
* @return void
*/
public function load_alert_triggers() {
$alert_triggers = array(
'author',
'context',
'action',
);
$classes = array();
foreach ( $alert_triggers as $alert_trigger ) {
$file_location = $this->plugin->locations['dir'] . '/alerts/class-alert-trigger-' . $alert_trigger . '.php';
if ( file_exists( $file_location ) ) {
include_once $file_location;
$class_name = sprintf( '\WP_Stream\Alert_Trigger_%s', str_replace( '-', '_', $alert_trigger ) );
if ( ! class_exists( $class_name ) ) {
continue;
}
$class = new $class_name( $this->plugin );
if ( ! property_exists( $class, 'slug' ) ) {
continue;
}
$classes[ $class->slug ] = $class;
}
}
/**
* Allows for adding additional alert_triggers via classes that extend Notifier.
*
* @param array $classes An array of Notifier objects. In the format alert_trigger_slug => Notifier_Class()
*/
$this->alert_triggers = apply_filters( 'wp_stream_alert_triggers', $classes );
// Ensure that all alert_triggers extend Notifier.
foreach ( $this->alert_triggers as $key => $alert_trigger ) {
if ( ! $this->is_valid_alert_trigger( $alert_trigger ) ) {
unset( $this->alert_triggers[ $key ] );
}
}
}
/**
* Checks whether a Alert Type class is valid
*
* @param Alert_Type $alert_type The class to check.
*
* @return bool
*/
public function is_valid_alert_type( $alert_type ) {
if ( ! is_a( $alert_type, 'WP_Stream\Alert_Type' ) ) {
return false;
}
if ( ! method_exists( $alert_type, 'is_dependency_satisfied' ) || ! $alert_type->is_dependency_satisfied() ) {
return false;
}
return true;
}
/**
* Checks whether a Alert Trigger class is valid
*
* @param Alert_Trigger $alert_trigger The class to check.
*
* @return bool
*/
public function is_valid_alert_trigger( $alert_trigger ) {
if ( ! is_a( $alert_trigger, 'WP_Stream\Alert_Trigger' ) ) {
return false;
}
if ( ! method_exists( $alert_trigger, 'is_dependency_satisfied' ) || ! $alert_trigger->is_dependency_satisfied() ) {
return false;
}
return true;
}
/**
* Checks record being processed against active alerts.
*
* @filter wp_stream_record_inserted
*
* @param int $record_id The record being processed.
* @param array $recordarr Record data.
*
* @return array
*/
public function check_records( $record_id, $recordarr ) {
$args = array(
'post_type' => self::POST_TYPE,
'post_status' => 'wp_stream_enabled',
);
$alerts = new \WP_Query( $args );
foreach ( $alerts->posts as $alert ) {
$alert = $this->get_alert( $alert->ID );
if ( false === $alert ) {
continue;
}
$status = $alert->check_record( $record_id, $recordarr );
if ( $status ) {
$alert->send_alert( $record_id, $recordarr ); // @todo send_alert expects int, not array.
}
}
return $recordarr;
}
/**
* Register scripts for page load
*
* @action admin_enqueue_scripts
*
* @return void
*/
public function register_scripts() {
$screen = get_current_screen();
if ( 'edit-wp_stream_alerts' === $screen->id ) {
$min = wp_stream_min_suffix();
wp_register_script(
'wp-stream-alerts',
$this->plugin->locations['url'] . 'ui/js/alerts.' . $min . 'js',
array(
'wp-stream-select2',
'jquery',
'inline-edit-post',
),
$this->plugin->get_version(),
false
);
wp_localize_script(
'wp-stream-alerts',
'streamAlerts',
array(
'any' => __( 'Any', 'stream' ),
'anyContext' => __( 'Any Context', 'stream' ),
)
);
wp_enqueue_script( 'wp-stream-alerts' );
wp_enqueue_style( 'wp-stream-select2' );
}
}
/**
* Register custom post type
*
* @action init
*
* @return void
*/
public function register_post_type() {
$labels = array(
'name' => _x( 'Alerts', 'post type general name', 'stream' ),
'singular_name' => _x( 'Alert', 'post type singular name', 'stream' ),
'menu_name' => _x( 'Alerts', 'admin menu', 'stream' ),
'name_admin_bar' => _x( 'Alert', 'add new on admin bar', 'stream' ),
'add_new' => _x( 'Add New', 'book', 'stream' ),
'add_new_item' => __( 'Add New Alert', 'stream' ),
'new_item' => __( 'New Alert', 'stream' ),
'edit_item' => __( 'Edit Alert', 'stream' ),
'view_item' => __( 'View Alert', 'stream' ),
'all_items' => __( 'Alerts', 'stream' ),
'search_items' => __( 'Search Alerts', 'stream' ),
'parent_item_colon' => __( 'Parent Alerts:', 'stream' ),
'not_found' => __( 'No alerts found.', 'stream' ),
'not_found_in_trash' => __( 'No alerts found in Trash.', 'stream' ),
);
$args = array(
'labels' => $labels,
'description' => __( 'Alerts for Stream.', 'stream' ),
'public' => false,
'publicly_queryable' => false,
'exclude_from_search' => true,
'show_ui' => true,
'show_in_menu' => false, // @see modify_admin_menu
'supports' => false,
'capabilities' => array(
'publish_posts' => self::CAPABILITY,
'edit_others_posts' => self::CAPABILITY,
'delete_posts' => self::CAPABILITY,
'delete_others_posts' => self::CAPABILITY,
'read_private_posts' => self::CAPABILITY,
'edit_post' => self::CAPABILITY,
'delete_post' => self::CAPABILITY,
'read_post' => self::CAPABILITY,
),
);
register_post_type( self::POST_TYPE, $args );
$args = array(
'label' => _x( 'Enabled', 'alert', 'stream' ),
'public' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
/* translators: %s: a number of items (e.g. "42") */
'label_count' => _n_noop( 'Enabled <span class="count">(%s)</span>', 'Enabled <span class="count">(%s)</span>', 'stream' ),
);
register_post_status( 'wp_stream_enabled', $args );
$args = array(
'label' => _x( 'Disabled', 'alert', 'stream' ),
'public' => false,
'internal' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
/* translators: %s: a number of items (e.g. "42") */
'label_count' => _n_noop( 'Disabled <span class="count">(%s)</span>', 'Disabled <span class="count">(%s)</span>', 'stream' ),
);
register_post_status( 'wp_stream_disabled', $args );
}
/**
* Return alert object of the given ID
*
* @param string $post_id Post ID for the alert.
*
* @return Alert
*/
public function get_alert( $post_id = '' ) {
if ( ! $post_id ) {
$obj = new Alert( null, $this->plugin );
return $obj;
}
$post = get_post( $post_id );
$alert_type = get_post_meta( $post_id, 'alert_type', true );
$alert_meta = get_post_meta( $post_id, 'alert_meta', true );
$obj = (object) array(
'ID' => $post->ID,
'status' => $post->post_status,
'date' => $post->post_date,
'author' => $post->post_author,
'alert_type' => $alert_type,
'alert_meta' => $alert_meta,
);
return new Alert( $obj, $this->plugin );
}
/**
* Add custom post type to menu
*
* @action admin_menu
*
* @return void
*/
public function register_menu() {
add_submenu_page(
$this->plugin->admin->records_page_slug,
__( 'Alerts', 'stream' ),
__( 'Alerts', 'stream' ),
self::CAPABILITY,
'edit.php?post_type=wp_stream_alerts'
);
}
/**
* Modify the Stream > Alerts Network Admin Menu link.
*
* In self::register_menu(), the Alerts submenu item
* is essentially set to go to the Site's admin area.
*
* However, on the Network admin, we need to redirect
* it to the first site in the network, as this is
* where the true Network Alerts settings page is located.
*
* @action network_admin_menu
* @return bool
*/
public function change_menu_link_url() {
global $submenu;
$parent = 'wp_stream';
$page = 'edit.php?post_type=wp_stream_alerts';
// If we're not on the Stream menu item, return.
if ( ! isset( $submenu[ $parent ] ) ) {
return false;
}
// Get the first existing Site in the Network.
$sites = wp_stream_get_sites(
array(
'limit' => 5, // Limit the size of the query.
)
);
$site_id = '1';
// Function wp_get_sites() can return an empty array if the network is too large.
if ( ! empty( $sites ) && ! empty( $sites[0]->blog_id ) ) {
$site_id = $sites[0]->blog_id;
}
$new_url = get_admin_url( $site_id, $page );
foreach ( $submenu[ $parent ] as $key => $value ) {
// Set correct URL for the menu item.
if ( $page === $value[2] ) {
// This hack is not kosher, see the docblock for an explanation.
$submenu[ $parent ][ $key ][2] = $new_url; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
break;
}
}
return true;
}
/**
* Display Alert Type Meta Box
*
* @param \WP_Post|array $post Post object for current alert.
*
* @return void
*/
public function display_notification_box( $post = array() ) {
$alert = null;
$alert_type = 'none';
if ( is_object( $post ) ) {
$alert = $this->get_alert( $post->ID );
if ( false !== $alert ) {
$alert_type = $alert->alert_type;
}
}
$form = new Form_Generator();
$field_html = $form->render_field(
'select',
array(
'id' => 'wp_stream_alert_type',
'name' => 'wp_stream_alert_type',
'value' => $alert_type,
'options' => $this->get_notification_values(),
'placeholder' => __( 'No Alert', 'stream' ),
'title' => 'Alert Type:',
)
);
echo '<label>' . esc_html__( 'Alert me by', 'stream' ) . '</label>';
echo $field_html; // Xss ok.
echo '<div id="wp_stream_alert_type_form">';
if ( is_object( $alert ) ) {
$alert->get_alert_type_obj()->display_fields( $alert );
} else {
$this->plugin->alerts->alert_types['none']->display_fields( array() );
}
echo '</div>';
}
/**
* Returns settings form HTML for AJAX use
*
* @action wp_ajax_load_alerts_settings
*
* @return void
*/
public function load_alerts_settings() {
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_send_json_error(
array(
'message' => 'You do not have permission to do this.',
)
);
}
$alert = array();
$post_id = wp_stream_filter_input( INPUT_POST, 'post_id' );
if ( ! empty( $post_id ) ) {
$alert = $this->get_alert( $post_id );
if ( false === $alert ) {
wp_send_json_error(
array(
'message' => 'Could not find alert.',
)
);
}
}
$alert_type = wp_stream_filter_input( INPUT_POST, 'alert_type' );
if ( empty( $alert_type ) ) {
$alert_type = 'none';
}
if ( ! array_key_exists( $alert_type, $this->alert_types ) ) {
wp_send_json_error(
array(
'message' => 'Could not find alert type.',
)
);
}
ob_start();
$this->alert_types[ $alert_type ]->display_fields( $alert );
$output = ob_get_contents();
ob_end_clean();
$data = array(
'html' => $output,
);
wp_send_json_success( $data );
}
/**
* Display Trigger Meta Box
*
* @param \WP_Post|array $post Post object for current alert.
*
* @return void
*/
public function display_triggers_box( $post = array() ) {
$alert = false;
if ( is_object( $post ) ) {
$alert = $this->get_alert( $post->ID );
}
if ( false === $alert ) {
$alert = array();
}
$form = new Form_Generator();
do_action( 'wp_stream_alert_trigger_form_display', $form, $alert );
// @TODO use human readable text.
echo '<label>' . esc_html__( 'Alert me when', 'stream' ) . '</label>';
echo $form->render_fields(); // Xss ok.
wp_nonce_field( 'save_alert', 'wp_stream_alerts_nonce' );
}
/**
* Display Submit Box
*
* @param \WP_Post $post Post object for current alert.
*
* @return void
*/
public function display_submit_box( $post ) {
if ( empty( $post ) ) {
return;
}
$post_status = $post->post_status;
if ( 'auto-draft' === $post_status ) {
$post_status = 'wp_stream_enabled';
}
?>
<div class="submitbox" id="submitpost">
<div id="minor-publishing">
<div id="misc-publishing-actions">
<div class="misc-pub-section misc-pub-post-status">
<label for="wp_stream_alert_status"><?php esc_html_e( 'Status', 'stream' ); ?></label>
<select name='wp_stream_alert_status' id='wp_stream_alert_status'>
<option<?php selected( $post_status, 'wp_stream_enabled' ); ?>
value='wp_stream_enabled'><?php esc_html_e( 'Enabled', 'stream' ); ?></option>
<option<?php selected( $post_status, 'wp_stream_disabled' ); ?>
value='wp_stream_disabled'><?php esc_html_e( 'Disabled', 'stream' ); ?></option>
</select>
</div>
</div>
<div class="clear"></div>
</div>
<div id="major-publishing-actions">
<div id="delete-action">
<?php
if ( current_user_can( 'delete_post', $post->ID ) ) {
if ( ! EMPTY_TRASH_DAYS ) {
$delete_text = __( 'Delete Permanently', 'stream' );
} else {
$delete_text = __( 'Move to Trash', 'stream' );
}
?>
<a class="submitdelete deletion" href="<?php echo get_delete_post_link( $post->ID ); ?>">
<?php esc_html( $delete_text ); ?>
</a>
<?php
}
?>
</div>
<div id="publishing-action">
<span class="spinner"></span>
<?php submit_button( __( 'Save' ), 'primary button-large', 'publish', false ); ?>
</div>
<div class="clear"></div>
</div>
</div>
<?php
}
/**
* Display Status Box
*
* @return void
*/
public function display_status_box() {
?>
<div id="minor-publishing">
<div id="misc-publishing-actions">
<div class="misc-pub-section misc-pub-post-status">
<label for="wp_stream_alert_status">
<span class="title"><?php esc_html_e( 'Status:', 'stream' ); ?></span>
<span class="input-text-wrap">
<select name='wp_stream_alert_status' id='wp_stream_alert_status'>
<option selected value='wp_stream_enabled'><?php esc_html_e( 'Enabled', 'stream' ); ?></option>
<option value='wp_stream_disabled'><?php esc_html_e( 'Disabled', 'stream' ); ?></option>
</select>
</span>
</label>
</div>
</div>
<div class="clear"></div>
</div>
<?php
}
/**
* Return all notification values
*
* @return array
*/
public function get_notification_values() {
$result = array();
$names = wp_list_pluck( $this->alert_types, 'name', 'slug' );
foreach ( $names as $slug => $name ) {
$result[ $slug ] = $name;
}
return $result;
}
/**
* Update actions dropdown options based on the connector selected.
*/
public function get_actions() {
$connector_name = wp_stream_filter_input( INPUT_POST, 'connector' );
$stream_connectors = wp_stream_get_instance()->connectors;
if ( ! empty( $connector_name ) ) {
if ( isset( $stream_connectors->connectors[ $connector_name ] ) ) {
$connector = $stream_connectors->connectors[ $connector_name ];
if ( method_exists( $connector, 'get_action_labels' ) ) {
$actions = $connector->get_action_labels();
}
}
} else {
$actions = $stream_connectors->term_labels['stream_action'];
}
ksort( $actions );
wp_send_json_success( $actions );
}
/**
* Save a new alert
*/
public function save_new_alert() {
check_ajax_referer( 'save_alert', 'wp_stream_alerts_nonce' );
if ( ! current_user_can( $this->plugin->admin->settings_cap ) ) {
wp_die(
esc_html__( "You don't have sufficient privileges to do this action.", 'stream' )
);
}
$trigger_author = wp_stream_filter_input( INPUT_POST, 'wp_stream_trigger_author' );
$trigger_connector_and_context = wp_stream_filter_input( INPUT_POST, 'wp_stream_trigger_context' );
if ( false !== strpos( $trigger_connector_and_context, '-' ) ) {
// This is a connector with a context such as posts-post.
$trigger_connector_and_context_split = explode( '-', $trigger_connector_and_context );
$trigger_connector = $trigger_connector_and_context_split[0];
$trigger_context = $trigger_connector_and_context_split[1];
} else {
if ( ! empty( $trigger_connector_and_context ) ) {
// This is a parent connector with no dash such as posts.
$trigger_connector = $trigger_connector_and_context;
$trigger_context = '';
} else {
// There is no connector or context.
$trigger_connector = '';
$trigger_context = '';
}
}
$trigger_action = wp_stream_filter_input( INPUT_POST, 'wp_stream_trigger_action' );
$alert_type = wp_stream_filter_input( INPUT_POST, 'wp_stream_alert_type' );
$alert_status = wp_stream_filter_input( INPUT_POST, 'wp_stream_alert_status' );
// Insert the post into the database.
$item = (object) array(
'alert_type' => $alert_type,
'alert_meta' => array(
'trigger_author' => $trigger_author,
'trigger_connector' => $trigger_connector,
'trigger_action' => $trigger_action,
'trigger_context' => $trigger_context,
),
'alert_status' => $alert_status,
);
$alert = new Alert( $item, $this->plugin );
$title = $alert->get_title();
$post_id = wp_insert_post(
array(
'post_status' => $alert_status,
'post_type' => 'wp_stream_alerts',
'post_title' => $title,
)
);
if ( empty( $post_id ) ) {
wp_send_json_error();
}
add_post_meta( $post_id, 'alert_type', $alert_type );
$alert_meta = array(
'trigger_author' => $trigger_author,
'trigger_connector' => $trigger_connector,
'trigger_action' => $trigger_action,
'trigger_context' => $trigger_context,
);
$alert_meta = apply_filters( 'wp_stream_alerts_save_meta', $alert_meta, $alert_type );
add_post_meta( $post_id, 'alert_meta', $alert_meta );
wp_send_json_success(
array(
'success' => true,
)
);
}
/**
* Return HTML string of the Alert page controls.
*/
public function get_new_alert_triggers_notifications() {
if ( ! current_user_can( $this->plugin->admin->settings_cap ) ) {
wp_die(
esc_html__( "You don't have sufficient privileges to do this action.", 'stream' )
);
}
ob_start();
?>
<fieldset class="inline-edit-col inline-edit-wp_stream_alerts inline-edit-add-new-triggers">
<legend class="inline-edit-legend">Add New</legend>
<?php $GLOBALS['wp_stream']->alerts->display_triggers_box(); ?>
</fieldset>
<fieldset class="inline-edit-col inline-edit-wp_stream_alerts inline-edit-add-new-notifications">
<?php $GLOBALS['wp_stream']->alerts->display_notification_box(); ?>
</fieldset>
<fieldset class="inline-edit-col inline-edit-wp_stream_alerts inline-edit-add-new-status">
<?php $GLOBALS['wp_stream']->alerts->display_status_box(); ?>
</fieldset>
<?php
$html = ob_get_clean();
wp_send_json_success(
array(
'success' => true,
'html' => $html,
)
);
}
/**
* Add action links to Stream drop row in admin list screen
*
* @filter wp_stream_action_links_{connector}
*
* @param array $links Previous links registered.
* @param Record $record Stream record.
*
* @return array Action links
*/
public function change_alert_action_links( $links, $record ) {
$post = get_post( $record->object_id );
if ( $post && self::POST_TYPE === $post->post_type && $post->post_status === $record->get_meta( 'new_status', true ) ) {
if ( 'trash' !== $post->post_status ) {
$connector_posts = new \WP_Stream\Connector_Posts();
$post_type_name = $connector_posts->get_post_type_name( get_post_type( $post->ID ) );
/* translators: %s: the post type singular name (e.g. "Post") */
$links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'stream' ), $post_type_name ) ] = admin_url( 'edit.php?post_type=wp_stream_alerts#post-' . $post->ID );
unset( $links[ esc_html__( 'View', 'stream' ) ] );
}
}
return $links;
}
}