PluginProbe
Stream – Activity Log & Audit Trail / 3.4.2
Stream – Activity Log & Audit Trail v3.4.2
4.4.0 4.3.0 4.2.2 4.2.1 trunk 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.1 3.1.1 3.10.0 3.2.0 3.2.1 3.2.2 3.2.3 All 50 releases
← All changes | classes/class-settings.php +90 -372 trunk3.4.2 View file →
@@ -1,24 +1,16 @@
1 1 <?php
2 -/**
3 - * Renders and manages the plugin Settings page.
4 - *
5 - * @package WP_Stream
6 - */
7 2
8 3 namespace WP_Stream;
9 4
10 -use WP_Roles;
11 -use WP_User;
12 -use WP_User_Query;
5 +use \WP_Roles;
6 +use \WP_User;
7 +use \WP_User_Query;
13 8
14 -/**
15 - * Class - Settings
16 - */
17 9 class Settings {
18 10
19 11 /**
20 - * Holds instance of plugin object
12 + * Hold Plugin class
21 13 *
22 14 * @var Plugin
23 15 */
24 16 public $plugin;
@@ -53,9 +45,9 @@
53 45
54 46 /**
55 47 * Class constructor.
56 48 *
57 - * @param Plugin $plugin Instance of plugin object.
49 + * @param Plugin $plugin The main Plugin class.
58 50 */
59 51 public function __construct( $plugin ) {
60 52 $this->plugin = $plugin;
61 53
@@ -61,12 +53,12 @@
61 53
62 54 $this->option_key = $this->get_option_key();
63 55 $this->options = $this->get_options();
64 56
65 - // Register settings, and fields.
57 + // Register settings, and fields
66 58 add_action( 'admin_init', array( $this, 'register_settings' ) );
67 59
68 - // Remove records when records TTL is shortened.
60 + // Remove records when records TTL is shortened
69 61 add_action(
70 62 'update_option_' . $this->option_key,
71 63 array(
72 64 $this,
@@ -75,9 +67,9 @@
75 67 10,
76 68 2
77 69 );
78 70
79 - // Apply label translations for settings.
71 + // Apply label translations for settings
80 72 add_filter(
81 73 'wp_stream_serialized_labels',
82 74 array(
83 75 $this,
@@ -84,12 +76,12 @@
84 76 'get_settings_translations',
85 77 )
86 78 );
87 79
88 - // Ajax callback function to search users.
80 + // Ajax callback function to search users
89 81 add_action( 'wp_ajax_stream_get_users', array( $this, 'get_users' ) );
90 82
91 - // Ajax callback function to search IPs.
83 + // Ajax callback function to search IPs
92 84 add_action( 'wp_ajax_stream_get_ips', array( $this, 'get_ips' ) );
93 85 }
94 86
95 87 /**
@@ -111,9 +103,9 @@
111 103
112 104 $search = '';
113 105 $input = wp_stream_filter_input( INPUT_POST, 'find' );
114 106
115 - if ( isset( $input['term'] ) ) {
107 + if ( ! isset( $input['term'] ) ) {
116 108 $search = wp_unslash( trim( $input['term'] ) );
117 109 }
118 110
119 111 $request = (object) array(
@@ -172,9 +164,9 @@
172 164 $response->users = array();
173 165 $users_added_to_response = array();
174 166
175 167 foreach ( $users_array as $key => $user ) {
176 - // exclude duplications.
168 + // exclude duplications:
177 169 if ( array_key_exists( $user->ID, $users_added_to_response ) ) {
178 170 continue;
179 171 } else {
180 172 $users_added_to_response[ $user->ID ] = true;
@@ -188,9 +180,9 @@
188 180 );
189 181
190 182 $args['tooltip'] = esc_attr(
191 183 sprintf(
192 - /* translators: %1$d: user ID, %2$s: username, %3$s: email, %4$s: user role (e.g. "42", "administrator", "foo@bar.com", "subscriber") */
184 + // translators: Placeholders refers to a user ID, a username, an email address, and a user role (e.g. "42", "administrator", "foo@bar.com", "subscriber").
193 185 __( 'ID: %1$d\nUser: %2$s\nEmail: %3$s\nRole: %4$s', 'stream' ),
194 186 $author->id,
195 187 $author->user_login,
196 188 $author->user_email,
@@ -354,15 +346,29 @@
354 346 'desc' => esc_html__( 'WordPress will automatically prevent duplicate comments from flooding the database. By default, Stream does not track these attempts unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'stream' ),
355 347 'after_field' => esc_html__( 'Enabled', 'stream' ),
356 348 'default' => 0,
357 349 ),
358 - $this->build_delete_all_records_field(),
359 - $this->build_clean_orphan_meta_field(),
350 + array(
351 + 'name' => 'delete_all_records',
352 + 'title' => esc_html__( 'Reset Stream Database', 'stream' ),
353 + 'type' => 'link',
354 + 'href' => add_query_arg(
355 + array(
356 + 'action' => 'wp_stream_reset',
357 + 'wp_stream_nonce_reset' => wp_create_nonce( 'stream_nonce_reset' ),
358 + ),
359 + admin_url( 'admin-ajax.php' )
360 + ),
361 + 'class' => 'warning',
362 + 'desc' => esc_html__( 'Warning: This will delete all activity records from the database.', 'stream' ),
363 + 'default' => 0,
364 + 'sticky' => 'bottom',
365 + ),
360 366 ),
361 367 ),
362 368 );
363 369
364 - // If Akismet is active, allow Admins to opt-in to Akismet tracking.
370 + // If Akismet is active, allow Admins to opt-in to Akismet tracking
365 371 if ( class_exists( 'Akismet' ) ) {
366 372 $akismet_tracking = array(
367 373 'name' => 'akismet_tracking',
368 374 'title' => esc_html__( 'Akismet Tracking', 'stream' ),
@@ -385,33 +391,8 @@
385 391 );
386 392
387 393 array_push( $fields['advanced']['fields'], $wp_cron_tracking );
388 394
389 - // Abilities API toggle is only meaningful on WordPress 6.9+. On
390 - // network-activated multisite, Abilities::is_enabled() reads the
391 - // network option (wp_stream_network), so a per-site checkbox on the
392 - // site's own settings screen would be a no-op and misleading. Hide
393 - // the field from per-site settings pages, but keep it available in
394 - // network admin and in REST/CLI contexts where update_all_setting_values()
395 - // routes writes to the network option correctly.
396 - $hide_per_site = $this->plugin->is_network_activated() && is_admin() && ! is_network_admin();
397 -
398 - if (
399 - class_exists( '\WP_Ability' )
400 - && ! $hide_per_site
401 - ) {
402 - $enable_abilities_api = array(
403 - 'name' => 'enable_abilities_api',
404 - 'title' => esc_html__( 'Enable Abilities API and MCP', 'stream' ),
405 - 'type' => 'checkbox',
406 - 'desc' => esc_html__( 'Expose Stream operations to AI agents via the WordPress Abilities API (and MCP when the MCP Adapter plugin is installed). Requires WordPress 6.9.', 'stream' ),
407 - 'after_field' => esc_html__( 'Enabled', 'stream' ),
408 - 'default' => 0,
409 - );
410 -
411 - array_push( $fields['advanced']['fields'], $enable_abilities_api );
412 - }
413 -
414 395 /**
415 396 * Filter allows for modification of options fields
416 397 *
417 398 * @return array Array of option fields
@@ -417,9 +398,9 @@
417 398 * @return array Array of option fields
418 399 */
419 400 $this->fields = apply_filters( 'wp_stream_settings_option_fields', $fields );
420 401
421 - // Sort option fields in each tab by title ASC.
402 + // Sort option fields in each tab by title ASC
422 403 foreach ( $this->fields as $tab => $options ) {
423 404 $titles = array();
424 405
425 406 foreach ( $options['fields'] as $field ) {
@@ -438,170 +419,8 @@
438 419 return $this->fields;
439 420 }
440 421
441 422 /**
442 - * Build the "Reset Stream Database" settings field definition.
443 - *
444 - * Extracted so the async-deletion running-state check
445 - * ({@see Admin::is_running_async_deletion()}) is evaluated once per render
446 - * instead of once per field property, and only in admin context.
447 - *
448 - * `Settings::__construct` populates `$this->options = $this->get_options()`
449 - * on the `init` hook for every pageload, which walks `get_fields()`. The
450 - * field is only ever rendered in admin, so outside admin the dynamic state
451 - * is irrelevant and the Action Scheduler query is skipped entirely.
452 - *
453 - * @return array
454 - */
455 - private function build_delete_all_records_field() {
456 - $is_running_deletion = is_admin() ? Admin::is_running_async_deletion() : false;
457 -
458 - return array(
459 - 'name' => 'delete_all_records',
460 - 'title' => esc_html__( 'Reset Stream Database', 'stream' ),
461 - 'type' => $is_running_deletion ? 'none' : 'link',
462 - 'href' => add_query_arg(
463 - array(
464 - 'action' => 'wp_stream_reset',
465 - 'wp_stream_nonce_reset' => wp_create_nonce( 'stream_nonce_reset' ),
466 - ),
467 - admin_url( 'admin-ajax.php' )
468 - ),
469 - 'class' => 'warning',
470 - 'desc' => esc_html( $this->get_deletion_warning( $is_running_deletion ) ),
471 - 'default' => 0,
472 - 'sticky' => 'bottom',
473 - );
474 - }
475 -
476 - /**
477 - * Build the "Clean Orphaned Meta" settings field definition.
478 - *
479 - * Extracted so the auto-purge running-state check
480 - * ({@see Admin::is_running_auto_purge()}) is evaluated once per render
481 - * instead of once per field property, and only in admin context — the
482 - * field is never rendered outside admin, so the Action Scheduler query
483 - * is skipped on front-end pageloads.
484 - *
485 - * @return array
486 - */
487 - private function build_clean_orphan_meta_field() {
488 - $is_running = is_admin() ? Admin::is_running_auto_purge() : false;
489 -
490 - return array(
491 - 'name' => 'clean_orphan_meta',
492 - 'title' => esc_html__( 'Clean Orphaned Meta', 'stream' ),
493 - 'type' => $is_running ? 'none' : 'link',
494 - 'href' => add_query_arg(
495 - array(
496 - 'action' => 'wp_stream_clean_orphan_meta',
497 - 'wp_stream_nonce_clean_orphan_meta' => wp_create_nonce( 'stream_nonce_clean_orphan_meta' ),
498 - ),
499 - admin_url( 'admin-ajax.php' )
500 - ),
501 - 'desc' => $is_running
502 - ? esc_html__( 'Auto-purge is currently running. The orphan reaper will execute as part of that cycle; the manual cleanup link is hidden to avoid duplicating the work.', 'stream' )
503 - : esc_html__( 'Schedules an immediate background cleanup of stream_meta rows whose parent record is missing. Safe to run while Stream is in use; runs once via Action Scheduler.', 'stream' ),
504 - 'default' => 0,
505 - 'sticky' => 'bottom',
506 - );
507 - }
508 -
509 - /**
510 - * Returns a single setting value, reading the network-level option when
511 - * Stream is network-activated on multisite.
512 - *
513 - * Settings::get_options() only loads from get_site_option() inside
514 - * is_network_admin() screens. In REST and frontend contexts on a
515 - * network-activated install, $this->options reflects the (typically empty)
516 - * per-site option, which would silently mask a network-admin-controlled
517 - * setting. This accessor handles that case so callers don't have to
518 - * duplicate the multisite branching.
519 - *
520 - * @param string $key Fully-qualified setting key (e.g. "advanced_enable_abilities_api").
521 - * @param mixed $default_value Value returned when the setting is not present.
522 - *
523 - * @return mixed
524 - */
525 - public function get_setting_value( $key, $default_value = null ) {
526 - if (
527 - is_multisite()
528 - && isset( $this->plugin )
529 - && $this->plugin->is_network_activated()
530 - ) {
531 - $options = (array) get_site_option( $this->network_options_key, array() );
532 - } else {
533 - $options = (array) $this->options;
534 - }
535 -
536 - return isset( $options[ $key ] ) ? $options[ $key ] : $default_value;
537 - }
538 -
539 - /**
540 - * Returns the full options array, reading the network-level option when
541 - * Stream is network-activated on multisite. Mirrors get_setting_value()
542 - * but returns the entire array.
543 - *
544 - * @return array
545 - */
546 - public function get_all_setting_values() {
547 - if (
548 - is_multisite()
549 - && isset( $this->plugin )
550 - && $this->plugin->is_network_activated()
551 - ) {
552 - return (array) get_site_option( $this->network_options_key, array() );
553 - }
554 -
555 - return (array) $this->options;
556 - }
557 -
558 - /**
559 - * Persists the options array, writing to the network-level option when
560 - * Stream is network-activated on multisite. Used by REST/ability writers
561 - * which run outside is_network_admin() but must respect the authoritative
562 - * store. Refreshes $this->options afterwards so in-request reads see the
563 - * new values.
564 - *
565 - * @param array $options Full options array to persist (caller is responsible
566 - * for merging over existing values when desired).
567 - *
568 - * @return bool True on a successful write, false on no-op or failure.
569 - */
570 - public function update_all_setting_values( array $options ) {
571 - $is_network = (
572 - is_multisite()
573 - && isset( $this->plugin )
574 - && $this->plugin->is_network_activated()
575 - );
576 -
577 - if ( $is_network ) {
578 - $result = update_site_option( $this->network_options_key, $options );
579 - } else {
580 - $result = update_option( $this->option_key, $options );
581 - }
582 -
583 - // Refresh the in-memory copy so subsequent reads in the same request
584 - // see the updated values. On network-activated installs we re-read
585 - // from the network option directly because Settings::get_options()
586 - // gates on is_network_admin() and would return the (now-stale)
587 - // per-site option in REST contexts. Merge defaults on top so callers
588 - // reading $plugin->settings->options keep seeing a fully-populated
589 - // array (matches get_options()'s historical contract).
590 - if ( $is_network ) {
591 - $defaults = $this->get_defaults( $this->option_key );
592 - $this->options = wp_parse_args(
593 - (array) get_site_option( $this->network_options_key, array() ),
594 - $defaults
595 - );
596 - } else {
597 - $this->options = $this->get_options();
598 - }
599 -
600 - return (bool) $result;
601 - }
602 -
603 - /**
604 423 * Returns a list of options based on the current screen.
605 424 *
606 425 * @return array
607 426 */
@@ -644,47 +463,8 @@
644 463 return (array) $defaults;
645 464 }
646 465
647 466 /**
648 - * Retrieves the deletion warning message based on the site type
649 - * and whether or not there is currently a process running to delete the tables.
650 - *
651 - * @param bool|null $is_running_deletion Optional pre-computed deletion state.
652 - * Pass to avoid a duplicate Action Scheduler
653 - * query when the caller has already checked.
654 - * Defaults to checking only in admin context.
655 - * Untyped parameter to remain compatible with
656 - * phpcs.xml.dist testVersion=7.0- (nullable
657 - * type declarations require PHP 7.1+).
658 - * @return string The deletion warning message.
659 - */
660 - public function get_deletion_warning( $is_running_deletion = null ): string {
661 -
662 - if ( null === $is_running_deletion ) {
663 - $is_running_deletion = is_admin() ? Admin::is_running_async_deletion() : false;
664 - }
665 -
666 - if ( $is_running_deletion ) {
667 -
668 - $warning = __( 'Currently deleting records. Please be patient, this can take a while.', 'stream' );
669 -
670 - } elseif ( $this->plugin->is_multisite_network_activated() ) {
671 -
672 - $warning = __( 'Warning: This will delete all activity records from the database for all sites.', 'stream' );
673 -
674 - } elseif ( $this->plugin->is_multisite_not_network_activated() ) {
675 -
676 - $warning = __( 'Warning: This will delete all activity records from the database for this site.', 'stream' );
677 -
678 - } else {
679 -
680 - $warning = __( 'Warning: This will delete all activity records from the database.', 'stream' );
681 - }
682 -
683 - return $warning;
684 - }
685 -
686 - /**
687 467 * Registers settings fields and sections
688 468 *
689 469 * @return void
690 470 */
@@ -708,10 +488,9 @@
708 488 $this->option_key
709 489 );
710 490
711 491 foreach ( $section['fields'] as $field_idx => $field ) {
712 - // No field type associated, skip, no GUI.
713 - if ( ! isset( $field['type'] ) ) {
492 + if ( ! isset( $field['type'] ) ) { // No field type associated, skip, no GUI
714 493 continue;
715 494 }
716 495
717 496 add_settings_field(
@@ -725,8 +504,9 @@
725 504 $section_name,
726 505 $field + array(
727 506 'section' => $section_name,
728 507 'label_for' => sprintf( '%s_%s_%s', $this->option_key, $section_name, $field['name'] ),
508 + // xss ok
729 509 )
730 510 );
731 511 }
732 512 }
@@ -734,9 +514,9 @@
734 514
735 515 /**
736 516 * Sanitization callback for settings field values before save
737 517 *
738 - * @param array $input Raw input.
518 + * @param array $input
739 519 *
740 520 * @return array
741 521 */
742 522 public function sanitize_settings( $input ) {
@@ -755,56 +535,41 @@
755 535 if ( empty( $type ) || ! isset( $input[ $name ] ) || '' === $input[ $name ] ) {
756 536 continue;
757 537 }
758 538
759 - $output[ $name ] = $this->sanitize_setting_by_field_type( $input[ $name ], $type );
760 - }
761 - }
539 + // Sanitize depending on the type of field.
540 + switch ( $type ) {
541 + case 'number':
542 + $output[ $name ] = is_numeric( $input[ $name ] ) ? intval( trim( $input[ $name ] ) ) : '';
543 + break;
544 + case 'checkbox':
545 + $output[ $name ] = is_numeric( $input[ $name ] ) ? absint( trim( $input[ $name ] ) ) : '';
546 + break;
547 + default:
548 + if ( is_array( $input[ $name ] ) ) {
549 + $output[ $name ] = $input[ $name ];
762 550
763 - return $output;
764 - }
765 -
766 - /**
767 - * Sanitizes a setting value based on the field type.
768 - *
769 - * @param mixed $value The value to be sanitized.
770 - * @param string $field_type The type of field.
771 - *
772 - * @return mixed The sanitized value.
773 - */
774 - public function sanitize_setting_by_field_type( $value, $field_type ) {
775 -
776 - // Sanitize depending on the type of field.
777 - switch ( $field_type ) {
778 - case 'number':
779 - $sanitized_value = is_numeric( $value ) ? intval( trim( $value ) ) : '';
780 - break;
781 - case 'checkbox':
782 - $sanitized_value = is_numeric( $value ) ? absint( trim( $value ) ) : '';
783 - break;
784 - default:
785 - if ( is_array( $value ) ) {
786 - $sanitized_value = $value;
787 -
788 - // Support all values in multidimentional arrays too.
789 - array_walk_recursive(
790 - $sanitized_value,
791 - function ( &$v ) {
792 - $v = sanitize_text_field( trim( $v ) );
551 + // Support all values in multidimentional arrays too.
552 + array_walk_recursive(
553 + $output[ $name ],
554 + function ( &$v ) {
555 + $v = sanitize_text_field( trim( $v ) );
556 + }
557 + );
558 + } else {
559 + $output[ $name ] = sanitize_text_field( trim( $input[ $name ] ) );
793 560 }
794 - );
795 - } else {
796 - $sanitized_value = sanitize_text_field( trim( $value ) );
797 561 }
562 + }
798 563 }
799 564
800 - return $sanitized_value;
565 + return $output;
801 566 }
802 567
803 568 /**
804 569 * Compile HTML needed for displaying the field
805 570 *
806 - * @param array $field Field settings.
571 + * @param array $field Field settings
807 572 *
808 573 * @return string HTML to be displayed
809 574 */
810 575 public function render_field( $field ) {
@@ -827,12 +592,14 @@
827 592 $nonce = isset( $field['nonce'] ) ? $field['nonce'] : null;
828 593
829 594 if ( isset( $field['value'] ) ) {
830 595 $current_value = $field['value'];
831 - } elseif ( isset( $this->options[ $section . '_' . $name ] ) ) {
596 + } else {
597 + if ( isset( $this->options[ $section . '_' . $name ] ) ) {
832 598 $current_value = $this->options[ $section . '_' . $name ];
833 - } else {
834 - $current_value = null;
599 + } else {
600 + $current_value = null;
601 + }
835 602 }
836 603
837 604 $option_key = $this->option_key;
838 605
@@ -979,16 +746,8 @@
979 746 esc_attr( $href ),
980 747 esc_attr( $title )
981 748 );
982 749 break;
983 - case 'none':
984 - // Intentional no-op: callers set 'none' to hide a control's value
985 - // column while still letting the row label + description render
986 - // (e.g. Reset Stream Database while a deletion is running, or
987 - // Clean Orphaned Meta while the auto-purge chain is active).
988 - // The description string carries the running-state message.
989 - $output = '';
990 - break;
991 750 case 'select2':
992 751 if ( ! isset( $current_value ) ) {
993 752 $current_value = '';
994 753 }
@@ -1034,12 +793,12 @@
1034 793 '<input type="hidden" name="%1$s[%2$s_%3$s]" data-values=\'%4$s\' value="%5$s" class="select2-select %6$s" data-placeholder="%7$s" />',
1035 794 esc_attr( $option_key ),
1036 795 esc_attr( $section ),
1037 796 esc_attr( $name ),
1038 - esc_attr( wp_json_encode( $data_values ) ),
797 + esc_attr( wp_stream_json_encode( $data_values ) ),
1039 798 esc_attr( $current_value ),
1040 799 esc_attr( $class ),
1041 - /* translators: %s: the title of the dropdown menu (e.g. "users") */
800 + // translators: Placeholder refers to the title of the dropdown menu (e.g. "users")
1042 801 sprintf( esc_html__( 'Any %s', 'stream' ), $title )
1043 802 );
1044 803
1045 804 $output = sprintf(
@@ -1098,9 +857,9 @@
1098 857 $context = isset( $current_value['context'][ $key ] ) ? $current_value['context'][ $key ] : '';
1099 858 $action = isset( $current_value['action'][ $key ] ) ? $current_value['action'][ $key ] : '';
1100 859 $ip_address = isset( $current_value['ip_address'][ $key ] ) ? $current_value['ip_address'][ $key ] : '';
1101 860
1102 - // Author or Role dropdown menu.
861 + // Author or Role dropdown menu
1103 862 $author_or_role_values = array();
1104 863 $author_or_role_selected = array();
1105 864
1106 865 foreach ( $this->get_roles() as $role_id => $role ) {
@@ -1110,9 +869,9 @@
1110 869 );
1111 870 $count = isset( $users['avail_roles'][ $role_id ] ) ? $users['avail_roles'][ $role_id ] : 0;
1112 871
1113 872 if ( ! empty( $count ) ) {
1114 - /* translators: %d: a number of users (e.g. "42") */
873 + // translators: Placeholder refers to a number of users (e.g. "42")
1115 874 $args['user_count'] = sprintf( _n( '%d user', '%d users', absint( $count ), 'stream' ), absint( $count ) );
1116 875 }
1117 876
1118 877 if ( $role_id === $author_or_role ) {
@@ -1138,20 +897,18 @@
1138 897 array(
1139 898 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'author_or_role' ) ),
1140 899 'options' => $author_or_role_values,
1141 900 'classes' => 'author_or_role',
1142 - // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
1143 901 'data' => array(
1144 - 'placeholder' => __( 'Any Author or Role', 'stream' ),
1145 - 'nonce' => wp_create_nonce( 'stream_get_users' ),
1146 - 'selected-id' => isset( $author_or_role_selected['value'] ) ? $author_or_role_selected['value'] : '',
1147 - 'selected-text' => isset( $author_or_role_selected['text'] ) ? $author_or_role_selected['text'] : '',
902 + 'placeholder' => esc_html__( 'Any Author or Role', 'stream' ),
903 + 'nonce' => esc_attr( wp_create_nonce( 'stream_get_users' ) ),
904 + 'selected-id' => isset( $author_or_role_selected['value'] ) ? esc_attr( $author_or_role_selected['value'] ) : '',
905 + 'selected-text' => isset( $author_or_role_selected['text'] ) ? esc_attr( $author_or_role_selected['text'] ) : '',
1148 906 ),
1149 - ),
1150 - false
907 + )
1151 908 );
1152 909
1153 - // Context dropdown menu.
910 + // Context dropdown menu
1154 911 $context_values = array();
1155 912
1156 913 foreach ( $this->get_terms_labels( 'context' ) as $context_id => $context_data ) {
1157 914 if ( is_array( $context_data ) ) {
@@ -1186,15 +943,13 @@
1186 943 array(
1187 944 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector_or_context' ) ),
1188 945 'options' => $context_values,
1189 946 'classes' => 'connector_or_context',
1190 - // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
1191 947 'data' => array(
1192 948 'group' => 'connector',
1193 949 'placeholder' => __( 'Any Context', 'stream' ),
1194 950 ),
1195 - ),
1196 - false
951 + )
1197 952 );
1198 953
1199 954 $connector_input = $form->render_field(
1200 955 'hidden',
@@ -1201,10 +956,9 @@
1201 956 array(
1202 957 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector' ) ),
1203 958 'value' => $connector,
1204 959 'classes' => 'connector',
1205 - ),
1206 - false
960 + )
1207 961 );
1208 962
1209 963 $context_input = $form->render_field(
1210 964 'hidden',
@@ -1211,13 +965,12 @@
1211 965 array(
1212 966 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'context' ) ),
1213 967 'value' => $context,
1214 968 'classes' => 'context',
1215 - ),
1216 - false
969 + )
1217 970 );
1218 971
1219 - // Action dropdown menu.
972 + // Action dropdown menu
1220 973 $action_values = array();
1221 974
1222 975 foreach ( $this->get_terms_labels( 'action' ) as $action_id => $action_data ) {
1223 976 $action_values[] = array(
@@ -1232,17 +985,15 @@
1232 985 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'action' ) ),
1233 986 'value' => $action,
1234 987 'options' => $action_values,
1235 988 'classes' => 'action',
1236 - // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
1237 989 'data' => array(
1238 990 'placeholder' => __( 'Any Action', 'stream' ),
1239 991 ),
1240 - ),
1241 - false
992 + )
1242 993 );
1243 994
1244 - // IP Address input.
995 + // IP Address input
1245 996 $ip_address_input = $form->render_field(
1246 997 'select2',
1247 998 array(
1248 999 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ),
@@ -1247,19 +998,17 @@
1247 998 array(
1248 999 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ),
1249 1000 'value' => $ip_address,
1250 1001 'classes' => 'ip_address',
1251 - // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
1252 1002 'data' => array(
1253 - 'placeholder' => __( 'Any IP Address', 'stream' ),
1254 - 'nonce' => wp_create_nonce( 'stream_get_ips' ),
1003 + 'placeholder' => esc_attr__( 'Any IP Address', 'stream' ),
1004 + 'nonce' => esc_attr( wp_create_nonce( 'stream_get_ips' ) ),
1255 1005 ),
1256 1006 'multiple' => true,
1257 - ),
1258 - false
1007 + )
1259 1008 );
1260 1009
1261 - // Hidden helper input.
1010 + // Hidden helper input
1262 1011 $helper_input = sprintf(
1263 1012 '<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" value="" />',
1264 1013 esc_attr( $option_key ),
1265 1014 esc_attr( $section ),
@@ -1273,11 +1022,9 @@
1273 1022 <td>%5$s</td>
1274 1023 <td>%6$s %7$s %8$s</td>
1275 1024 <td>%9$s</td>
1276 1025 <td>%10$s</td>
1277 - <th scope="row" class="actions-column">
1278 - <a href="#" class="exclude_rules_remove_rule_row">%11$s</a>
1279 - </th>
1026 + <th scope="row" class="actions-column">%11$s</th>
1280 1027 </tr>',
1281 1028 ( 0 !== (int) $key % 2 ) ? 'alternate' : '',
1282 1029 ( 'helper' === (string) $key ) ? 'hidden helper' : '',
1283 1030 '<input class="cb-select" type="checkbox" />',
@@ -1287,9 +1034,9 @@
1287 1034 $connector_input,
1288 1035 $context_input,
1289 1036 $action_input,
1290 1037 $ip_address_input,
1291 - esc_html__( 'Delete', 'stream' )
1038 + '<a href="#" class="exclude_rules_remove_rule_row">Delete</a>'
1292 1039 );
1293 1040 }
1294 1041
1295 1042 $no_rules_found_row = sprintf(
@@ -1314,9 +1061,9 @@
1314 1061
1315 1062 /**
1316 1063 * Render Callback for post_types field
1317 1064 *
1318 - * @param array $field Field to be rendered.
1065 + * @param array $field
1319 1066 *
1320 1067 * @return string
1321 1068 */
1322 1069 public function output_field( $field ) {
@@ -1327,9 +1074,9 @@
1327 1074 }
1328 1075
1329 1076 $output = $this->render_field( $field );
1330 1077
1331 - echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1078 + echo $output; // xss ok
1332 1079 }
1333 1080
1334 1081 /**
1335 1082 * Get an array of user roles
@@ -1349,9 +1096,9 @@
1349 1096
1350 1097 /**
1351 1098 * Function will return all terms labels of given column
1352 1099 *
1353 - * @param string $column Name of the column.
1100 + * @param string $column string Name of the column
1354 1101 *
1355 1102 * @return array
1356 1103 */
1357 1104 public function get_terms_labels( $column ) {
@@ -1384,10 +1131,10 @@
1384 1131 * Remove records when records TTL is shortened
1385 1132 *
1386 1133 * @action update_option_wp_stream
1387 1134 *
1388 - * @param array $old_value Old value.
1389 - * @param array $new_value New value.
1135 + * @param array $old_value
1136 + * @param array $new_value
1390 1137 */
1391 1138 public function updated_option_ttl_remove_records( $old_value, $new_value ) {
1392 1139 $ttl_before = isset( $old_value['general_records_ttl'] ) ? (int) $old_value['general_records_ttl'] : - 1;
1393 1140 $ttl_after = isset( $new_value['general_records_ttl'] ) ? (int) $new_value['general_records_ttl'] : - 1;
@@ -1393,38 +1140,11 @@
1393 1140 $ttl_after = isset( $new_value['general_records_ttl'] ) ? (int) $new_value['general_records_ttl'] : - 1;
1394 1141
1395 1142 if ( $ttl_after < $ttl_before ) {
1396 1143 /**
1397 - * Fires when the records TTL is shortened.
1398 - *
1399 - * Preserved for backward compatibility with third-party code that
1400 - * hooked this action in Stream <= 4.1.x. The auto-purge itself
1401 - * no longer listens to this hook (it was migrated to Action
1402 - * Scheduler), so trigger the purge directly below.
1144 + * Action assists in purging when TTL is shortened
1403 1145 */
1404 1146 do_action( 'wp_stream_auto_purge' );
1405 -
1406 - // Trigger an immediate auto-purge cycle so the shortened TTL
1407 - // takes effect now instead of at the next 12h recurring tick.
1408 - //
1409 - // Enqueue the recurring action as a one-shot async action so the
1410 - // work serializes through the scheduler. Calling
1411 - // purge_scheduled_action() inline here would bypass the overlap
1412 - // guard's view of "in-flight" work (the current request is not a
1413 - // scheduled action) and could stack a parallel chain when a
1414 - // real chain is already running. Falls back to inline if no
1415 - // scheduler is available (defensive — Plugin::__construct() sets it).
1416 - if ( ! empty( $this->plugin->scheduler ) ) {
1417 - if ( ! \WP_Stream\Admin::is_running_auto_purge() ) {
1418 - $this->plugin->scheduler->enqueue_async(
1419 - \WP_Stream\Admin::AUTO_PURGE_ACTION,
1420 - array(),
1421 - \WP_Stream\Admin::AUTO_PURGE_GROUP
1422 - );
1423 - }
1424 - } elseif ( isset( $this->plugin->admin ) ) {
1425 - $this->plugin->admin->purge_scheduled_action();
1426 - }
1427 1147 }
1428 1148 }
1429 1149
1430 1150 /**
@@ -1430,10 +1150,8 @@
1430 1150 /**
1431 1151 * Get translations of serialized Stream settings
1432 1152 *
1433 1153 * @filter wp_stream_serialized_labels
1434 - *
1435 - * @param array $labels Setting labels.
1436 1154 *
1437 1155 * @return array Multidimensional array of fields
1438 1156 */
1439 1157 public function get_settings_translations( $labels ) {