diff --git a/classes/class-alerts-admin-ui.php b/classes/class-alerts-admin-ui.php
new file mode 100644
index 000000000..d01075424
--- /dev/null
+++ b/classes/class-alerts-admin-ui.php
@@ -0,0 +1,579 @@
+register_hooks();
+ }
+
+ /**
+ * Register menu, script, AJAX, and action-link hooks.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ // 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_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',
+ )
+ );
+
+ add_filter(
+ 'wp_stream_action_links_posts',
+ array(
+ $this,
+ 'change_alert_action_links',
+ ),
+ 11,
+ 2
+ );
+ }
+
+ /**
+ * 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 ) {
+ return;
+ }
+
+ $this->plugin->enqueue_asset(
+ 'alerts',
+ array(
+ $this->plugin->with_select2(),
+ 'inline-edit-post',
+ ),
+ array(
+ 'any' => __( 'Any', 'stream' ),
+ 'anyContext' => __( 'Any Context', 'stream' ),
+ 'getActionsNonce' => wp_create_nonce( 'stream_get_actions' ),
+ )
+ );
+ }
+
+ /**
+ * 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' ),
+ Alerts::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->plugin->alerts->get_alert( $post->ID );
+ if ( false !== $alert ) {
+ $alert_type = $alert->alert_type;
+ }
+ }
+ $form = new Form_Generator();
+
+ echo '';
+ $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 '
';
+ if ( is_object( $alert ) ) {
+ $alert->get_alert_type_obj()->display_fields( $alert );
+ } else {
+ $this->plugin->alerts->alert_types['none']->display_fields( array() );
+ }
+
+ echo '
';
+ }
+
+ /**
+ * Returns settings form HTML for AJAX use
+ *
+ * @action wp_ajax_load_alerts_settings
+ *
+ * @return void
+ */
+ public function load_alerts_settings() {
+ if ( ! current_user_can( Alerts::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 ) && 'new' !== $post_id ) {
+ $alert = $this->plugin->alerts->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->plugin->alerts->alert_types ) ) {
+ wp_send_json_error(
+ array(
+ 'message' => 'Could not find alert type.',
+ )
+ );
+ }
+
+ ob_start();
+ $this->plugin->alerts->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->plugin->alerts->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 '';
+ $form->render_fields();
+ wp_nonce_field( 'save_alert', 'wp_stream_alerts_nonce' );
+
+ if ( $post instanceof \WP_Post ) :
+ /**
+ * These fields are required for the post to be saved, as the Admin AJAX inline_save action is fired.
+ *
+ * @see get_inline_data()
+ * @see wp_ajax_inline_save()
+ */
+ ?>
+
+
+
+
+
+
+
+ post_status;
+ if ( 'auto-draft' === $post_status ) {
+ $post_status = 'wp_stream_enabled';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID ) ) {
+ if ( ! EMPTY_TRASH_DAYS ) {
+ $delete_text = __( 'Delete Permanently', 'stream' );
+ } else {
+ $delete_text = __( 'Move to Trash', 'stream' );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ plugin->alerts->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() {
+ if ( ! current_user_can( Alerts::CAPABILITY ) ) {
+ wp_send_json_error(
+ array(
+ 'message' => 'You do not have permission to do this.',
+ )
+ );
+ }
+
+ check_ajax_referer( 'stream_get_actions', 'nonce' );
+
+ $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];
+ } elseif ( ! 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();
+ ?>
+
+
+
+ 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 && Alerts::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;
+ }
+}
diff --git a/classes/class-alerts-list.php b/classes/class-alerts-list.php
index e5a60a317..753e1ad22 100644
--- a/classes/class-alerts-list.php
+++ b/classes/class-alerts-list.php
@@ -312,7 +312,7 @@ public function display_custom_quick_edit() {
plugin->alerts, $function_name ), $the_post );
+ call_user_func( array( $this->plugin->alerts->admin_ui, $function_name ), $the_post );
?>
register_hooks();
+ }
+
+ /**
+ * Register record-insert matching hooks.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ add_filter(
+ 'wp_stream_record_inserted',
+ array(
+ $this,
+ 'check_records',
+ ),
+ 10,
+ 2
+ );
+ }
+
+ /**
+ * 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' => Alerts::POST_TYPE,
+ 'post_status' => 'wp_stream_enabled',
+ );
+
+ $alerts = new \WP_Query( $args );
+ $hydrated = array();
+ foreach ( $alerts->posts as $alert ) {
+ $hydrated[] = $this->plugin->alerts->get_alert( $alert->ID );
+ }
+
+ foreach ( $this->matching_alerts( $hydrated, $record_id, $recordarr ) as $alert ) {
+ $alert->send_alert( $record_id, $recordarr ); // @todo send_alert expects int, not array.
+ }
+
+ return $recordarr;
+ }
+
+ /**
+ * Return alerts whose check_record() result is true.
+ *
+ * Matching goes through Alert::check_record() so the
+ * wp_stream_alert_trigger_check filter still applies.
+ *
+ * @param array $alerts Hydrated Alert objects (false entries are skipped).
+ * @param int $record_id The record being processed.
+ * @param array $recordarr Record data.
+ * @return array Alert objects that matched.
+ */
+ public function matching_alerts( array $alerts, $record_id, array $recordarr ) {
+ $matches = array();
+ foreach ( $alerts as $alert ) {
+ if ( false === $alert ) {
+ continue;
+ }
+
+ $status = $alert->check_record( $record_id, $recordarr );
+ if ( $status ) {
+ $matches[] = $alert;
+ }
+ }
+
+ return $matches;
+ }
+}
diff --git a/classes/class-alerts.php b/classes/class-alerts.php
index 0d34ed7d5..a2c75ea99 100644
--- a/classes/class-alerts.php
+++ b/classes/class-alerts.php
@@ -41,6 +41,8 @@ class Alerts {
/**
* Post meta prefix
+ *
+ * @var string
*/
public string $meta_prefix = 'wp_stream';
@@ -58,81 +60,38 @@ class Alerts {
*/
public $alert_triggers = array();
+ /**
+ * Record-matching collaborator.
+ *
+ * Public so in-plugin callers can match alerts without an Alerts façade.
+ *
+ * @var Alerts_Trigger_Engine
+ */
+ public Alerts_Trigger_Engine $trigger_engine;
+
+ /**
+ * Admin forms / AJAX / menu collaborator.
+ *
+ * Public so in-plugin callers can render alert UI without an Alerts façade.
+ *
+ * @var Alerts_Admin_UI
+ */
+ public Alerts_Admin_UI $admin_ui;
+
/**
* Class constructor.
*
* @param Plugin $plugin Instance of plugin object.
*/
public function __construct( public $plugin ) {
+ $this->trigger_engine = new Alerts_Trigger_Engine( $this->plugin );
+ $this->admin_ui = new Alerts_Admin_UI( $this->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
- );
}
/**
@@ -263,66 +222,6 @@ public function is_valid_alert_trigger( $alert_trigger ) {
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 ) {
- return;
- }
-
- $this->plugin->enqueue_asset(
- 'alerts',
- array(
- $this->plugin->with_select2(),
- 'inline-edit-post',
- ),
- array(
- 'any' => __( 'Any', 'stream' ),
- 'anyContext' => __( 'Any Context', 'stream' ),
- 'getActionsNonce' => wp_create_nonce( 'stream_get_actions' ),
- )
- );
- }
-
/**
* Register custom post type
*
@@ -456,470 +355,4 @@ public function get_alerts( array $statuses = array() ) {
return $alerts;
}
-
- /**
- * 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();
-
- echo '';
- $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 '';
- if ( is_object( $alert ) ) {
- $alert->get_alert_type_obj()->display_fields( $alert );
- } else {
- $this->plugin->alerts->alert_types['none']->display_fields( array() );
- }
-
- echo '
';
- }
-
- /**
- * 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 ) && 'new' !== $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 '';
- $form->render_fields();
- wp_nonce_field( 'save_alert', 'wp_stream_alerts_nonce' );
-
- if ( $post instanceof \WP_Post ) :
- /**
- * These fields are required for the post to be saved, as the Admin AJAX inline_save action is fired.
- *
- * @see get_inline_data()
- * @see wp_ajax_inline_save()
- */
- ?>
-
-
-
-
-
-
-
- post_status;
- if ( 'auto-draft' === $post_status ) {
- $post_status = 'wp_stream_enabled';
- }
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
- ID ) ) {
- if ( ! EMPTY_TRASH_DAYS ) {
- $delete_text = __( 'Delete Permanently', 'stream' );
- } else {
- $delete_text = __( 'Move to Trash', 'stream' );
- }
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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() {
- if ( ! current_user_can( self::CAPABILITY ) ) {
- wp_send_json_error(
- array(
- 'message' => 'You do not have permission to do this.',
- )
- );
- }
-
- check_ajax_referer( 'stream_get_actions', 'nonce' );
-
- $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];
- } elseif ( ! 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();
- ?>
-
-
-
- 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;
- }
}
diff --git a/tests/phpunit/Alerts_Test.php b/tests/phpunit/Alerts_Test.php
index 958595322..638d85088 100644
--- a/tests/phpunit/Alerts_Test.php
+++ b/tests/phpunit/Alerts_Test.php
@@ -18,6 +18,8 @@ public function tearDown(): void {
public function test_construct() {
$alerts = new Alerts( $this->plugin );
$this->assertNotEmpty( $alerts->plugin );
+ $this->assertInstanceOf( Alerts_Trigger_Engine::class, $alerts->trigger_engine );
+ $this->assertInstanceOf( Alerts_Admin_UI::class, $alerts->admin_ui );
}
public function test_load_alert_types() {
@@ -100,21 +102,22 @@ public function test_is_valid_alert_trigger() {
}
public function test_check_records() {
- $this->markTestIncomplete(
- 'This test is incomplete.'
- );
- // WP_Query not finding active alerts.
+ $alerts = $this->plugin->alerts;
- $alerts = new Alerts( $this->plugin );
- $alert = new Alert( $this->dummy_alert_data(), $this->plugin );
- $alert->save();
+ $data = $this->dummy_alert_data();
+ $data->ID = 0;
+ $alert = new Alert( $data, $this->plugin );
+ $post_id = $alert->save();
+ $this->assertNotEmpty( $post_id );
$action = new \MockAction();
add_filter( 'wp_stream_alert_trigger_check', array( $action, 'filter' ) );
- $alerts->check_records( 0, $this->dummy_stream_data() );
+ $record = $this->dummy_stream_data();
+ $result = $alerts->trigger_engine->check_records( 0, $record );
- $this->assertEquals( 1, $action->get_call_count() );
+ $this->assertSame( $record, $result );
+ $this->assertGreaterThan( 0, $action->get_call_count() );
}
public function test_register_post_type() {
@@ -186,15 +189,18 @@ public function test_get_alert_blank() {
public function test_register_menu() {
global $submenu;
- $this->markTestIncomplete();
+ $slug = $this->plugin->admin->records_page_slug;
+ $this->plugin->alerts->admin_ui->register_menu();
- $this->assertEquals( array(), $submenu[ $this->plugin->admin->records_page_slug ] );
- $submenu[ $this->plugin->admin->records_page_slug ] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
- $this->assertEmpty( $submenu[ $this->plugin->admin->records_page_slug ] );
-
- $alerts = new Alerts( $this->plugin );
- $alerts->register_menu();
- $this->assertNotEmpty( $submenu[ $this->plugin->admin->records_page_slug ] );
+ $this->assertArrayHasKey( $slug, $submenu );
+ $found = false;
+ foreach ( $submenu[ $slug ] as $item ) {
+ if ( isset( $item[2] ) && 'edit.php?post_type=wp_stream_alerts' === $item[2] ) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue( $found, 'Alerts submenu points at the alerts post type list.' );
}
public function test_display_notification_box() {
@@ -206,18 +212,12 @@ public function test_display_notification_box() {
$post_id = $alert->save();
ob_start();
- $alerts->display_notification_box( get_post( $alert->ID ) );
- $output = ob_get_contents();
- ob_end_clean();
-
- $len_test = strlen( $output ) > 0;
- $this->assertTrue( $len_test, 'Output length greater than zero.' );
-
- $field_test = strpos( $output, 'wp_stream_alert_type' ) !== -1;
- $this->assertTrue( $len_test, 'Alert type field is present.' );
+ $alerts->admin_ui->display_notification_box( get_post( $alert->ID ) );
+ $output = ob_get_clean();
- $form_test = strpos( $output, 'wp_stream_alert_type_form' ) !== -1;
- $this->assertTrue( $form_test, 'Alert type settings form is present' );
+ $this->assertNotSame( '', $output );
+ $this->assertStringContainsString( 'wp_stream_alert_type', $output );
+ $this->assertStringContainsString( 'wp_stream_alert_type_form', $output );
}
public function test_load_alerts_settings() {
@@ -316,15 +316,11 @@ public function test_display_triggers_box() {
$post_id = $alert->save();
ob_start();
- $alerts->display_triggers_box( get_post( $alert->ID ) );
- $output = ob_get_contents();
- ob_end_clean();
+ $alerts->admin_ui->display_triggers_box( get_post( $alert->ID ) );
+ $output = ob_get_clean();
- $len_test = strlen( $output ) > 0;
- $this->assertTrue( $len_test, 'Output length greater than zero.' );
-
- $field_test = strpos( $output, 'wp_stream_alerts_nonce' ) !== -1;
- $this->assertTrue( $len_test, 'Nonce field is present.' );
+ $this->assertNotSame( '', $output );
+ $this->assertStringContainsString( 'wp_stream_alerts_nonce', $output );
}
public function test_display_submit_box() {
@@ -336,31 +332,21 @@ public function test_display_submit_box() {
$post_id = $alert->save();
ob_start();
- $alerts->display_submit_box( get_post( $alert->ID ) );
- $output = ob_get_contents();
- ob_end_clean();
-
- $len_test = strlen( $output ) > 0;
- $this->assertTrue( $len_test, 'Output length greater than zero.' );
+ $alerts->admin_ui->display_submit_box( get_post( $alert->ID ) );
+ $output = ob_get_clean();
- $field_test = strpos( $output, 'wp_stream_enabled' ) !== -1;
- $this->assertTrue( $len_test, 'Alert is shown as enabled.' );
+ $this->assertNotSame( '', $output );
+ $this->assertStringContainsString( 'wp_stream_enabled', $output );
}
public function test_get_notification_values() {
$alerts = new Alerts( $this->plugin );
$count = count( $alerts->alert_types );
- $output = $alerts->get_notification_values();
+ $output = $alerts->admin_ui->get_notification_values();
$this->assertEquals( $count, count( $output ) );
}
- public function test_save_post_info() {
- $this->markTestIncomplete(
- 'This test is incomplete'
- );
- }
-
public function test_get_actions() {
$user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
wp_set_current_user( $user_id );
@@ -565,6 +551,9 @@ public function test_get_new_alert_triggers_notifications() {
$this->assertIsObject( $response );
$this->assertObjectHasProperty( 'success', $response );
$this->assertTrue( $response->success );
+ $this->assertObjectHasProperty( 'html', $response->data );
+ $this->assertStringContainsString( 'wp_stream_alert_status', $response->data->html );
+ $this->assertStringContainsString( 'wp_stream_alert_type', $response->data->html );
}
public function test_get_new_alert_triggers_notifications_missing_caps() {
diff --git a/tests/phpunit/unit/Alerts_Admin_UI_Unit_Test.php b/tests/phpunit/unit/Alerts_Admin_UI_Unit_Test.php
new file mode 100644
index 000000000..ce2cd4328
--- /dev/null
+++ b/tests/phpunit/unit/Alerts_Admin_UI_Unit_Test.php
@@ -0,0 +1,313 @@
+stubTranslationFunctions();
+ $this->stubEscapeFunctions();
+
+ Functions\when( 'wp_list_pluck' )->alias( array( self::class, 'wp_list_pluck_stub' ) );
+ Functions\when( 'wp_parse_args' )->alias( array( self::class, 'wp_parse_args_stub' ) );
+ Functions\when( 'selected' )->alias( array( self::class, 'selected_stub' ) );
+ Functions\when( 'wp_nonce_field' )->alias( array( self::class, 'wp_nonce_field_stub' ) );
+
+ $this->plugin = Mockery::mock( Plugin::class );
+ $alerts = Mockery::mock( Alerts::class );
+ $alerts->alert_types = $this->sample_alert_types();
+ $this->plugin->alerts = $alerts;
+
+ $this->admin_ui = new Alerts_Admin_UI( $this->plugin );
+ }
+
+ /**
+ * Merge defaults like wp_parse_args.
+ *
+ * @param array $args Incoming args.
+ * @param array $defaults Defaults.
+ * @return array
+ */
+ public static function wp_parse_args_stub( $args, $defaults = array() ) {
+ return array_merge( $defaults, (array) $args );
+ }
+
+ /**
+ * selected() stand-in; third arg false returns the attribute.
+ *
+ * @param mixed $selected Compared value.
+ * @param mixed $current Current value.
+ * @param bool $echo Unused.
+ * @return string
+ */
+ public static function selected_stub( $selected, $current = true, $echo = true ) {
+ unset( $echo );
+ return ( $selected === $current ) ? ' selected="selected"' : '';
+ }
+
+ /**
+ * Echo a nonce field marker.
+ *
+ * @param string $action Action name.
+ * @param string $name Field name.
+ * @return void
+ */
+ public static function wp_nonce_field_stub( $action, $name ) {
+ unset( $action );
+ printf( '', esc_attr( $name ) );
+ }
+
+ /**
+ * WordPress wp_list_pluck stand-in.
+ *
+ * @param array $input_list List of objects or arrays.
+ * @param string $field Field to pluck.
+ * @param string|null $index_key Optional index field.
+ * @return array
+ */
+ public static function wp_list_pluck_stub( $input_list, $field, $index_key = null ) {
+ $result = array();
+ foreach ( $input_list as $key => $item ) {
+ $value = is_object( $item ) ? $item->{$field} : $item[ $field ];
+ if ( null !== $index_key ) {
+ $idx = is_object( $item ) ? $item->{$index_key} : $item[ $index_key ];
+ $result[ $idx ] = $value;
+ } else {
+ $result[ $key ] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Sample alert types with slug/name, matching production objects.
+ *
+ * @return array
+ */
+ protected function sample_alert_types() {
+ $none = new \stdClass();
+ $none->slug = 'none';
+ $none->name = 'Do Nothing';
+ $highlight = new \stdClass();
+ $highlight->slug = 'highlight';
+ $highlight->name = 'Highlight';
+ $email = new \stdClass();
+ $email->slug = 'email';
+ $email->name = 'Email';
+
+ return array(
+ 'none' => $none,
+ 'highlight' => $highlight,
+ 'email' => $email,
+ );
+ }
+
+ /**
+ * Maps type slug to display name.
+ *
+ * @param string $slug Type slug.
+ * @param string $expected Expected display name.
+ */
+ #[DataProvider( 'data_notification_slugs' )]
+ public function test_get_notification_values_maps_slug_to_name( $slug, $expected ) {
+ $values = $this->admin_ui->get_notification_values();
+
+ $this->assertArrayHasKey( $slug, $values );
+ $this->assertSame( $expected, $values[ $slug ] );
+ }
+
+ /**
+ * Slug → name pairs for get_notification_values.
+ *
+ * @return array
+ */
+ public static function data_notification_slugs() {
+ return array(
+ 'none' => array( 'none', 'Do Nothing' ),
+ 'highlight' => array( 'highlight', 'Highlight' ),
+ 'email' => array( 'email', 'Email' ),
+ );
+ }
+
+ public function test_get_notification_values_count_matches_alert_types() {
+ $values = $this->admin_ui->get_notification_values();
+
+ $this->assertCount( count( $this->plugin->alerts->alert_types ), $values );
+ }
+
+ public function test_display_status_box_outputs_enabled_and_disabled() {
+ ob_start();
+ $this->admin_ui->display_status_box();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'wp_stream_alert_status', $output );
+ $this->assertStringContainsString( 'wp_stream_enabled', $output );
+ $this->assertStringContainsString( 'wp_stream_disabled', $output );
+ }
+
+ public function test_display_submit_box_returns_early_when_post_empty() {
+ ob_start();
+ $this->admin_ui->display_submit_box( null );
+ $this->assertSame( '', ob_get_clean() );
+ }
+
+ public function test_display_notification_box_without_post_renders_type_select() {
+ $none = Mockery::mock();
+ $none->slug = 'none';
+ $none->name = 'Do Nothing';
+ $none->shouldReceive( 'display_fields' )->once()->with( array() );
+ $this->plugin->alerts->alert_types['none'] = $none;
+
+ ob_start();
+ $this->admin_ui->display_notification_box();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'wp_stream_alert_type', $output );
+ $this->assertStringContainsString( 'wp_stream_alert_type_form', $output );
+ $this->assertStringContainsString( 'Do Nothing', $output );
+ }
+
+ public function test_display_triggers_box_without_post_prints_nonce() {
+ ob_start();
+ $this->admin_ui->display_triggers_box();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'wp_stream_alerts_nonce', $output );
+ $this->assertStringContainsString( 'Alert me when', $output );
+ }
+
+ public function test_change_menu_link_url_returns_false_when_stream_menu_missing() {
+ $GLOBALS['submenu'] = array();
+
+ $this->assertFalse( $this->admin_ui->change_menu_link_url() );
+ }
+
+ public function test_change_menu_link_url_rewrites_alerts_item_to_first_site() {
+ $GLOBALS['submenu'] = array(
+ 'wp_stream' => array(
+ array( 'Alerts', 'manage_options', 'edit.php?post_type=wp_stream_alerts' ),
+ ),
+ );
+
+ Functions\when( 'wp_stream_get_sites' )->justReturn(
+ array(
+ (object) array(
+ 'blog_id' => '7',
+ ),
+ )
+ );
+ Functions\when( 'get_admin_url' )->alias(
+ static function ( $site_id, $page ) {
+ return 'https://example.test/site-' . $site_id . '/' . $page;
+ }
+ );
+
+ $this->assertTrue( $this->admin_ui->change_menu_link_url() );
+ $this->assertSame(
+ 'https://example.test/site-7/edit.php?post_type=wp_stream_alerts',
+ $GLOBALS['submenu']['wp_stream'][0][2]
+ );
+ }
+
+ public function test_change_menu_link_url_falls_back_to_site_one_when_sites_empty() {
+ $GLOBALS['submenu'] = array(
+ 'wp_stream' => array(
+ array( 'Alerts', 'manage_options', 'edit.php?post_type=wp_stream_alerts' ),
+ ),
+ );
+
+ Functions\when( 'wp_stream_get_sites' )->justReturn( array() );
+ Functions\when( 'get_admin_url' )->alias(
+ static function ( $site_id, $page ) {
+ return 'https://example.test/site-' . $site_id . '/' . $page;
+ }
+ );
+
+ $this->assertTrue( $this->admin_ui->change_menu_link_url() );
+ $this->assertSame(
+ 'https://example.test/site-1/edit.php?post_type=wp_stream_alerts',
+ $GLOBALS['submenu']['wp_stream'][0][2]
+ );
+ }
+
+ public function test_register_scripts_skips_enqueue_off_alerts_screen() {
+ Functions\when( 'get_current_screen' )->justReturn(
+ (object) array(
+ 'id' => 'edit-post',
+ )
+ );
+ $this->plugin->shouldReceive( 'enqueue_asset' )->never();
+
+ $this->admin_ui->register_scripts();
+ }
+
+ public function test_register_scripts_enqueues_alerts_asset_on_list_screen() {
+ Functions\when( 'get_current_screen' )->justReturn(
+ (object) array(
+ 'id' => 'edit-wp_stream_alerts',
+ )
+ );
+ Functions\when( 'wp_create_nonce' )->justReturn( 'stream-nonce' );
+ $this->plugin->shouldReceive( 'with_select2' )->once()->andReturn( array( 'select2' ) );
+ $this->plugin->shouldReceive( 'enqueue_asset' )
+ ->once()
+ ->with(
+ 'alerts',
+ array( array( 'select2' ), 'inline-edit-post' ),
+ Mockery::on(
+ static function ( $l10n ) {
+ return isset( $l10n['getActionsNonce'] ) && 'stream-nonce' === $l10n['getActionsNonce'];
+ }
+ )
+ );
+
+ $this->admin_ui->register_scripts();
+ }
+
+ public function test_change_alert_action_links_unchanged_when_post_missing() {
+ Functions\when( 'get_post' )->justReturn( null );
+ $record = Mockery::mock( Record::class );
+ $record->object_id = 12;
+ $links = array(
+ 'View' => 'https://example.test/view',
+ );
+
+ $this->assertSame( $links, $this->admin_ui->change_alert_action_links( $links, $record ) );
+ }
+
+ public function test_change_alert_action_links_unchanged_for_other_post_types() {
+ Functions\when( 'get_post' )->justReturn(
+ (object) array(
+ 'ID' => 4,
+ 'post_type' => 'post',
+ 'post_status' => 'publish',
+ )
+ );
+ $record = Mockery::mock( Record::class );
+ $record->object_id = 4;
+ $links = array(
+ 'View' => 'https://example.test/view',
+ );
+
+ $this->assertSame( $links, $this->admin_ui->change_alert_action_links( $links, $record ) );
+ }
+}
diff --git a/tests/phpunit/unit/Alerts_Trigger_Engine_Unit_Test.php b/tests/phpunit/unit/Alerts_Trigger_Engine_Unit_Test.php
new file mode 100644
index 000000000..eb2c4000e
--- /dev/null
+++ b/tests/phpunit/unit/Alerts_Trigger_Engine_Unit_Test.php
@@ -0,0 +1,226 @@
+plugin = Mockery::mock( Plugin::class );
+ $this->plugin->alerts = Mockery::mock( Alerts::class );
+ $this->engine = new Alerts_Trigger_Engine( $this->plugin );
+ }
+
+ protected function tear_down() {
+ Alerts_Trigger_Engine_Wp_Query_Stub::$posts_to_return = array();
+ Alerts_Trigger_Engine_Wp_Query_Stub::$last_args = null;
+ parent::tear_down();
+ }
+
+ /**
+ * Alias a test-local WP_Query so check_records() can run on the host.
+ *
+ * @return void
+ */
+ private static function stub_wp_query() {
+ if ( ! class_exists( \WP_Query::class, false ) ) {
+ class_alias( Alerts_Trigger_Engine_Wp_Query_Stub::class, 'WP_Query' );
+ }
+ }
+
+ public function test_register_hooks_attaches_check_records_to_record_inserted() {
+ $this->assertSame(
+ 10,
+ has_filter( 'wp_stream_record_inserted', array( $this->engine, 'check_records' ) )
+ );
+ }
+
+ /**
+ * Returns only alerts whose check_record() is true.
+ *
+ * @param array $outcomes Per-alert check_record results.
+ * @param int $expected_count Expected match count.
+ */
+ #[DataProvider( 'data_matching_outcomes' )]
+ public function test_matching_alerts_filters_by_check_record( $outcomes, $expected_count ) {
+ $record_id = 5;
+ $recordarr = array(
+ 'action' => 'updated',
+ );
+ $alerts = array();
+ foreach ( $outcomes as $matches ) {
+ $alert = Mockery::mock( Alert::class )->makePartial();
+ $alert->shouldReceive( 'check_record' )
+ ->once()
+ ->with( $record_id, $recordarr )
+ ->andReturn( $matches );
+ $alerts[] = $alert;
+ }
+
+ $result = $this->engine->matching_alerts( $alerts, $record_id, $recordarr );
+
+ $this->assertCount( $expected_count, $result );
+ foreach ( $result as $alert ) {
+ $this->assertContains( $alert, $alerts );
+ }
+ }
+
+ /**
+ * Match / no-match / mixed lists. Scalars only; mocks stay in the test.
+ *
+ * @return array, 1: int}>
+ */
+ public static function data_matching_outcomes() {
+ return array(
+ 'all_match' => array( array( true, true ), 2 ),
+ 'none_match' => array( array( false, false ), 0 ),
+ 'mixed' => array( array( true, false, true ), 2 ),
+ );
+ }
+
+ public function test_matching_alerts_empty_input_returns_empty() {
+ $this->assertSame(
+ array(),
+ $this->engine->matching_alerts( array(), 1, array() )
+ );
+ }
+
+ public function test_matching_alerts_skips_false_entries() {
+ $recordarr = array(
+ 'action' => 'updated',
+ );
+ $matching = Mockery::mock( Alert::class )->makePartial();
+ $matching->shouldReceive( 'check_record' )
+ ->once()
+ ->with( 7, $recordarr )
+ ->andReturn( true );
+
+ $result = $this->engine->matching_alerts( array( false, $matching ), 7, $recordarr );
+
+ $this->assertSame( array( $matching ), $result );
+ }
+
+ public function test_check_records_returns_recordarr_when_no_enabled_alerts() {
+ $recordarr = array(
+ 'action' => 'activated',
+ );
+
+ $this->plugin->alerts->shouldReceive( 'get_alert' )->never();
+
+ $result = $this->engine->check_records( 11, $recordarr );
+
+ $this->assertSame( $recordarr, $result );
+ $this->assertSame(
+ array(
+ 'post_type' => Alerts::POST_TYPE,
+ 'post_status' => 'wp_stream_enabled',
+ ),
+ Alerts_Trigger_Engine_Wp_Query_Stub::$last_args
+ );
+ }
+
+ /**
+ * Hydrates query posts and sends only matching alerts.
+ *
+ * @param array $outcomes Per-alert check_record results.
+ * @param int $expected_count Unused; shared provider with matching_alerts.
+ */
+ #[DataProvider( 'data_matching_outcomes' )]
+ public function test_check_records_sends_only_matching_alerts( $outcomes, $expected_count ) {
+ unset( $expected_count );
+ $record_id = 9;
+ $recordarr = array(
+ 'connector' => 'installer',
+ 'action' => 'activated',
+ );
+ $hydrated = array();
+
+ foreach ( $outcomes as $index => $matches ) {
+ $post_id = $index + 1;
+ Alerts_Trigger_Engine_Wp_Query_Stub::$posts_to_return[] = (object) array(
+ 'ID' => $post_id,
+ );
+
+ $alert = Mockery::mock( Alert::class )->makePartial();
+ $alert->shouldReceive( 'check_record' )
+ ->once()
+ ->with( $record_id, $recordarr )
+ ->andReturn( $matches );
+
+ if ( $matches ) {
+ $alert->shouldReceive( 'send_alert' )
+ ->once()
+ ->with( $record_id, $recordarr );
+ } else {
+ $alert->shouldReceive( 'send_alert' )->never();
+ }
+
+ $hydrated[ $post_id ] = $alert;
+ }
+
+ $this->plugin->alerts->shouldReceive( 'get_alert' )
+ ->times( count( $outcomes ) )
+ ->andReturnUsing(
+ static function ( $post_id ) use ( $hydrated ) {
+ return $hydrated[ $post_id ];
+ }
+ );
+
+ $result = $this->engine->check_records( $record_id, $recordarr );
+
+ $this->assertSame( $recordarr, $result );
+ }
+}
+
+/**
+ * Test-local WP_Query for Alerts_Trigger_Engine::check_records().
+ */
+class Alerts_Trigger_Engine_Wp_Query_Stub {
+
+ /**
+ * Posts returned by the next query.
+ *
+ * @var array
+ */
+ public static $posts_to_return = array();
+
+ /**
+ * Arguments passed to the last constructor call.
+ *
+ * @var array|null
+ */
+ public static $last_args;
+
+ /**
+ * Query posts.
+ *
+ * @var array
+ */
+ public $posts = array();
+
+ /**
+ * @param array $args WP_Query arguments.
+ */
+ public function __construct( $args = array() ) {
+ self::$last_args = $args;
+ $this->posts = self::$posts_to_return;
+ }
+}