diff --git a/alerts/class-alert-trigger-author.php b/alerts/class-alert-trigger-author.php index f00fe41b0..859245db4 100644 --- a/alerts/class-alert-trigger-author.php +++ b/alerts/class-alert-trigger-author.php @@ -37,7 +37,10 @@ class Alert_Trigger_Author extends Alert_Trigger { * @return bool False on failure, otherwise should return original value of $success. */ public function check_record( $success, $record_id, $recordarr, $alert ) { - if ( ! empty( $alert->alert_meta['trigger_author'] ) && intval( $alert->alert_meta['trigger_author'] ) !== intval( $recordarr['user_id'] ) ) { + // The stored value may be '0' (WP-CLI), which empty() would drop and + // turn the trigger into "any author". + $trigger_author = isset( $alert->alert_meta['trigger_author'] ) ? (string) $alert->alert_meta['trigger_author'] : ''; + if ( '' !== $trigger_author && (int) $trigger_author !== (int) $recordarr['user_id'] ) { return false; } @@ -60,12 +63,30 @@ public function add_fields( $form, $alert = array() ) { $value = $alert->alert_meta['trigger_author']; } + $picker = $this->plugin->user_picker->get( $this->plugin->admin->get_preload_users_max() ); + + // Over the preload cap: Ajax user combobox instead of a preloaded select. + if ( $picker['ajax'] ) { + $form->add_field( + 'user_combobox', + array( + 'name' => esc_attr( $this->field_key ), + 'value' => esc_attr( $value ), + 'selected_label' => $this->plugin->user_picker->label_for_value( $value ), + 'data' => array( + 'placeholder' => __( 'Any Author', 'stream' ), + ), + ) + ); + return; + } + $form->add_field( 'grouped_select', array( 'name' => esc_attr( $this->field_key ), 'value' => esc_attr( $value ), - 'options' => $this->get_values(), + 'options' => $this->append_stored_value_option( $this->get_values(), $value ), 'data' => array( 'placeholder' => __( 'Any Author', 'stream' ), ), @@ -113,7 +134,7 @@ function ( $login ) { $all_records[] = array( 'id' => $user->id, 'value' => $user->id, - 'text' => $user->get_display_name(), + 'text' => $this->plugin->user_picker->label( $user->id ), ); } @@ -131,11 +152,37 @@ function ( $login ) { */ public function save_fields( $alert ) { $input = wp_stream_filter_input( INPUT_POST, $this->field_key ); - if ( array_key_exists( $input, $this->get_values( $alert ) ) ) { - $alert->alert_meta['trigger_author'] = $input; - } else { - $alert->alert_meta['trigger_author'] = ''; + $input = is_scalar( $input ) ? (string) $input : ''; + + // Only a user ID (or 0 for WP-CLI) is stored; anything else clears the + // trigger. Membership checks are impossible in combobox (Ajax) mode, + // where the option list is not rendered server-side. + $alert->alert_meta['trigger_author'] = ctype_digit( $input ) ? $input : ''; + } + + /** + * Append the stored author to the option list when missing (e.g. deleted user). + * + * @param array $options Picker options. + * @param string $current Stored author id. + * @return array + */ + private function append_stored_value_option( array $options, $current ) { + if ( ! ctype_digit( (string) $current ) ) { + return $options; } + + $values = array_map( 'strval', array_column( $options, 'value' ) ); + if ( in_array( (string) $current, $values, true ) ) { + return $options; + } + + $options[] = array( + 'value' => (string) $current, + 'text' => $this->plugin->user_picker->label( (int) $current ), + ); + + return $options; } /** @@ -149,18 +196,17 @@ public function save_fields( $alert ) { * @return string */ public function get_display_value( $context = 'normal', $alert = null ) { - $author = ( ! empty( $alert->alert_meta['trigger_author'] ) ) ? $alert->alert_meta['trigger_author'] : null; - if ( empty( $author ) ) { - $author = __( 'Any User', 'stream' ); - } elseif ( is_numeric( $author ) ) { - $author_data = get_userdata( $author ); - if ( $author_data ) { - $author = $author_data->display_name; - } else { - $author = __( 'Unknown User', 'stream' ); - } + // Note: the stored value may be '0' (WP-CLI), which is falsy — use isset(). + $trigger_author = $alert?->alert_meta['trigger_author'] ?? ''; + + if ( '' === $trigger_author ) { + return __( 'Any User', 'stream' ); + } + + if ( ctype_digit( $trigger_author ) ) { + return $this->plugin->user_picker->label( (int) $trigger_author ); } - return ucfirst( $author ); + return ucfirst( $trigger_author ); } } diff --git a/changelog.md b/changelog.md index f4ecea5e1..9517e6fd5 100644 --- a/changelog.md +++ b/changelog.md @@ -9,7 +9,7 @@ ### Enhancements - Add an Outgoing Webhook alert (HTTP POST or PUT, optional headers, JSON body with record-field placeholders). IFTTT is no longer offered when creating a new alert; existing IFTTT alerts still fire. To keep using Maker, configure a webhook whose URL is `https://maker.ifttt.com/trigger/{event}/with/key/{key}`. -- Replace Select2 with native `` elements across the admin (records filters, settings exclude rules, alert triggers). Author/role filters offer every user in grouped native selects; IP exclusion rules accept a comma-separated list in a plain text field. Relative timestamps now use `Intl.RelativeTimeFormat` (locale-aware) with the same bold relative + absolute date presentation as before. The `select2` and `jquery-timeago` dependencies and their bundled vendor copies are gone, and the unused `stream_get_users` and `stream_get_ips` Ajax actions were removed. User pickers preload every user in a native ` alert_meta['trigger_author'] ) + ? (string) $alert->alert_meta['trigger_author'] + : ''; + ?> + + + custom_column_actions( $post_id ) ); break; case 'alert_type': diff --git a/classes/class-form-generator.php b/classes/class-form-generator.php index a9f58be95..e46dff58e 100644 --- a/classes/class-form-generator.php +++ b/classes/class-form-generator.php @@ -197,6 +197,9 @@ public function render_field( $field_type, $args, $echo_output = true ) { $output .= ''; break; + case 'user_combobox': + $output = $this->render_user_combobox( $args ); + break; case 'checkbox': $output = sprintf( '%3$s', @@ -221,6 +224,73 @@ public function render_field( $field_type, $args, $echo_output = true ) { echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + /** + * Accessible user search combobox (hidden value + search input + listbox). + * + * Optional `role_options` render as a "Roles" group inside the listbox + * (single control), mirroring the previous Select2 dropdown's grouping. + * The hidden input carries the submitted value: a user id or role slug. + * + * @param array $args Field args. + * @return string Markup. + */ + private function render_user_combobox( $args ) { + $placeholder = ''; + if ( ! empty( $args['data']['placeholder'] ) ) { + $placeholder = (string) $args['data']['placeholder']; + } + + $selected_label = isset( $args['selected_label'] ) ? (string) $args['selected_label'] : ''; + $search_label = $placeholder ? $placeholder : __( 'Search users', 'stream' ); + $value = (string) $args['value']; + $hidden_classes = trim( 'stream-user-combobox__value ' . (string) $args['classes'] ); + + $role_options_attr = ''; + if ( ! empty( $args['role_options'] ) && is_array( $args['role_options'] ) ) { + $role_data = array(); + foreach ( $args['role_options'] as $role_option ) { + $role_option = wp_parse_args( + $role_option, + array( + 'value' => '', + 'text' => '', + ) + ); + if ( '' === (string) $role_option['value'] ) { + continue; + } + $role_data[] = array( + 'value' => (string) $role_option['value'], + 'label' => (string) $role_option['text'], + ); + } + $role_options_attr = sprintf( + ' data-role-options="%s"', + esc_attr( wp_json_encode( $role_data ) ) + ); + } + + $hidden_id = ''; + if ( ! empty( $args['id'] ) ) { + $hidden_id = (string) $args['id']; + } elseif ( false === strpos( (string) $args['name'], '[' ) ) { + $hidden_id = (string) $args['name']; + } + $id_attr = '' !== $hidden_id ? sprintf( ' id="%s"', esc_attr( $hidden_id ) ) : ''; + + return sprintf( + '
', + esc_attr( $placeholder ), + esc_attr( $selected_label ), + $role_options_attr, + esc_attr( $search_label ), + esc_attr( $args['name'] ), + $id_attr, + esc_attr( $hidden_classes ), + esc_attr( $value ) + ); + } + /** * Render a single