PluginProbe
Stream – Activity Log & Audit Trail / 4.0.2
Stream – Activity Log & Audit Trail v4.0.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
stream / classes / class-settings.php

class-settings.php in Stream – Activity Log & Audit Trail 4.0.2, at classes/class-settings.php

1,206 lines 34.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Renders and manages the plugin Settings page.
4 *
5 * @package WP_Stream
6 */
7
8 namespace WP_Stream;
9
10 use WP_Roles;
11 use WP_User;
12 use WP_User_Query;
13
14 /**
15 * Class - Settings
16 */
17 class Settings {
18
19 /**
20 * Holds instance of plugin object
21 *
22 * @var Plugin
23 */
24 public $plugin;
25
26 /**
27 * Settings key/identifier
28 *
29 * @var string
30 */
31 public $option_key = 'wp_stream';
32
33 /**
34 * Network settings key/identifier
35 *
36 * @var string
37 */
38 public $network_options_key = 'wp_stream_network';
39
40 /**
41 * Plugin settings
42 *
43 * @var array
44 */
45 public $options = array();
46
47 /**
48 * Settings fields
49 *
50 * @var array
51 */
52 public $fields = array();
53
54 /**
55 * Class constructor.
56 *
57 * @param Plugin $plugin Instance of plugin object.
58 */
59 public function __construct( $plugin ) {
60 $this->plugin = $plugin;
61
62 $this->option_key = $this->get_option_key();
63 $this->options = $this->get_options();
64
65 // Register settings, and fields.
66 add_action( 'admin_init', array( $this, 'register_settings' ) );
67
68 // Remove records when records TTL is shortened.
69 add_action(
70 'update_option_' . $this->option_key,
71 array(
72 $this,
73 'updated_option_ttl_remove_records',
74 ),
75 10,
76 2
77 );
78
79 // Apply label translations for settings.
80 add_filter(
81 'wp_stream_serialized_labels',
82 array(
83 $this,
84 'get_settings_translations',
85 )
86 );
87
88 // Ajax callback function to search users.
89 add_action( 'wp_ajax_stream_get_users', array( $this, 'get_users' ) );
90
91 // Ajax callback function to search IPs.
92 add_action( 'wp_ajax_stream_get_ips', array( $this, 'get_ips' ) );
93 }
94
95 /**
96 * Ajax callback function to search users, used on exclude setting page
97 *
98 * @uses \WP_User_Query
99 */
100 public function get_users() {
101 if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
102 return;
103 }
104
105 check_ajax_referer( 'stream_get_users', 'nonce' );
106
107 $response = (object) array(
108 'status' => false,
109 'message' => esc_html__( 'There was an error in the request', 'stream' ),
110 );
111
112 $search = '';
113 $input = wp_stream_filter_input( INPUT_POST, 'find' );
114
115 if ( ! isset( $input['term'] ) ) {
116 $search = wp_unslash( trim( $input['term'] ) );
117 }
118
119 $request = (object) array(
120 'find' => $search,
121 );
122
123 add_filter(
124 'user_search_columns',
125 array(
126 $this,
127 'add_display_name_search_columns',
128 ),
129 10,
130 3
131 );
132
133 $users = new WP_User_Query(
134 array(
135 'search' => "*{$request->find}*",
136 'search_columns' => array(
137 'user_login',
138 'user_nicename',
139 'user_email',
140 'user_url',
141 ),
142 'orderby' => 'display_name',
143 'number' => $this->plugin->admin->preload_users_max,
144 )
145 );
146
147 remove_filter(
148 'user_search_columns',
149 array(
150 $this,
151 'add_display_name_search_columns',
152 ),
153 10
154 );
155
156 if ( 0 === $users->get_total() ) {
157 wp_send_json_error( $response );
158 }
159 $users_array = $users->results;
160
161 if ( is_multisite() && is_super_admin() ) {
162 $super_admins = get_super_admins();
163 foreach ( $super_admins as $admin ) {
164 $user = get_user_by( 'login', $admin );
165 $users_array[] = $user;
166 }
167 }
168
169 $response->status = true;
170 $response->message = '';
171 $response->roles = $this->get_roles();
172 $response->users = array();
173 $users_added_to_response = array();
174
175 foreach ( $users_array as $key => $user ) {
176 // exclude duplications.
177 if ( array_key_exists( $user->ID, $users_added_to_response ) ) {
178 continue;
179 } else {
180 $users_added_to_response[ $user->ID ] = true;
181 }
182
183 $author = new Author( $user->ID );
184
185 $args = array(
186 'id' => $author->ID,
187 'text' => $author->display_name,
188 );
189
190 $args['tooltip'] = esc_attr(
191 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") */
193 __( 'ID: %1$d\nUser: %2$s\nEmail: %3$s\nRole: %4$s', 'stream' ),
194 $author->id,
195 $author->user_login,
196 $author->user_email,
197 ucwords( $author->get_role() )
198 )
199 );
200
201 $args['icon'] = $author->get_avatar_src( 32 );
202
203 $response->users[] = $args;
204 }
205
206 usort(
207 $response->users,
208 function ( $a, $b ) {
209 return strcmp( $a['text'], $b['text'] );
210 }
211 );
212
213 if ( empty( $search ) || preg_match( '/wp|cli|system|unknown/i', $search ) ) {
214 $author = new Author( 0 );
215 $response->users[] = array(
216 'id' => '0',
217 'text' => $author->get_display_name(),
218 'icon' => $author->get_avatar_src( 32 ),
219 'tooltip' => esc_html__( 'Actions performed by the system when a user is not logged in (e.g. auto site upgrader, or invoking WP-CLI without --user)', 'stream' ),
220 );
221 }
222
223 wp_send_json_success( $response );
224 }
225
226 /**
227 * Ajax callback function to search IP addresses, used on exclude setting page
228 */
229 public function get_ips() {
230 if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
231 return;
232 }
233
234 check_ajax_referer( 'stream_get_ips', 'nonce' );
235
236 $ips = $this->plugin->db->existing_records( 'ip' );
237 $find = wp_stream_filter_input( INPUT_POST, 'find' );
238
239 if ( isset( $find['term'] ) && '' !== $find['term'] ) {
240 $ips = array_filter(
241 $ips,
242 function ( $ip ) use ( $find ) {
243 return 0 === strpos( $ip, $find['term'] );
244 }
245 );
246 }
247
248 if ( $ips ) {
249 wp_send_json_success( $ips );
250 } else {
251 wp_send_json_error();
252 }
253 }
254
255 /**
256 * Filter the columns to search in a WP_User_Query search.
257 *
258 * @param array $search_columns Array of column names to be searched.
259 * @param string $search Text being searched.
260 * @param \WP_User_Query $query current WP_User_Query instance.
261 *
262 * @return array
263 */
264 public function add_display_name_search_columns( $search_columns, $search, $query ) {
265 unset( $search );
266 unset( $query );
267
268 $search_columns[] = 'display_name';
269
270 return $search_columns;
271 }
272
273 /**
274 * Returns the option key
275 *
276 * @return string
277 */
278 public function get_option_key() {
279 $option_key = $this->option_key;
280
281 $current_page = wp_stream_filter_input( INPUT_GET, 'page' );
282
283 if ( ! $current_page ) {
284 $current_page = wp_stream_filter_input( INPUT_GET, 'action' );
285 }
286
287 if ( 'wp_stream_network_settings' === $current_page ) {
288 $option_key = $this->network_options_key;
289 }
290
291 return apply_filters( 'wp_stream_settings_option_key', $option_key );
292 }
293
294 /**
295 * Return settings fields
296 *
297 * @return array
298 */
299 public function get_fields() {
300 $fields = array(
301 'general' => array(
302 'title' => esc_html__( 'General', 'stream' ),
303 'fields' => array(
304 array(
305 'name' => 'role_access',
306 'title' => esc_html__( 'Role Access', 'stream' ),
307 'type' => 'multi_checkbox',
308 'desc' => esc_html__( 'Users from the selected roles above will have permission to view Stream Records. However, only site Administrators can access Stream Settings.', 'stream' ),
309 'choices' => $this->get_roles(),
310 'default' => array( 'administrator' ),
311 ),
312 array(
313 'name' => 'records_ttl',
314 'title' => esc_html__( 'Keep Records for', 'stream' ),
315 'type' => 'number',
316 'class' => 'small-text',
317 'desc' => esc_html__( 'Maximum number of days to keep activity records.', 'stream' ),
318 'default' => 30,
319 'min' => 1,
320 'max' => 999,
321 'step' => 1,
322 'after_field' => esc_html__( 'days', 'stream' ),
323 ),
324 array(
325 'name' => 'keep_records_indefinitely',
326 'title' => esc_html__( 'Keep Records Indefinitely', 'stream' ),
327 'type' => 'checkbox',
328 'desc' => sprintf( '<strong>%s</strong> %s', esc_html__( 'Not recommended.', 'stream' ), esc_html__( 'Purging old records helps to keep your WordPress installation running optimally.', 'stream' ) ),
329 'after_field' => esc_html__( 'Enabled', 'stream' ),
330 'default' => 0,
331 ),
332 ),
333 ),
334 'exclude' => array(
335 'title' => esc_html__( 'Exclude', 'stream' ),
336 'fields' => array(
337 array(
338 'name' => 'rules',
339 'title' => esc_html__( 'Exclude Rules', 'stream' ),
340 'type' => 'rule_list',
341 'desc' => esc_html__( 'Create rules to exclude certain kinds of activity from being recorded by Stream.', 'stream' ),
342 'default' => array(),
343 'nonce' => 'stream_get_ips',
344 ),
345 ),
346 ),
347 'advanced' => array(
348 'title' => esc_html__( 'Advanced', 'stream' ),
349 'fields' => array(
350 array(
351 'name' => 'comment_flood_tracking',
352 'title' => esc_html__( 'Comment Flood Tracking', 'stream' ),
353 'type' => 'checkbox',
354 '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 'after_field' => esc_html__( 'Enabled', 'stream' ),
356 'default' => 0,
357 ),
358 array(
359 'name' => 'delete_all_records',
360 'title' => esc_html__( 'Reset Stream Database', 'stream' ),
361 'type' => 'link',
362 'href' => add_query_arg(
363 array(
364 'action' => 'wp_stream_reset',
365 'wp_stream_nonce_reset' => wp_create_nonce( 'stream_nonce_reset' ),
366 ),
367 admin_url( 'admin-ajax.php' )
368 ),
369 'class' => 'warning',
370 'desc' => esc_html__( 'Warning: This will delete all activity records from the database.', 'stream' ),
371 'default' => 0,
372 'sticky' => 'bottom',
373 ),
374 ),
375 ),
376 );
377
378 // If Akismet is active, allow Admins to opt-in to Akismet tracking.
379 if ( class_exists( 'Akismet' ) ) {
380 $akismet_tracking = array(
381 'name' => 'akismet_tracking',
382 'title' => esc_html__( 'Akismet Tracking', 'stream' ),
383 'type' => 'checkbox',
384 'desc' => esc_html__( 'Akismet already keeps statistics for comment attempts that it blocks as SPAM. By default, Stream does not track these attempts unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'stream' ),
385 'after_field' => esc_html__( 'Enabled', 'stream' ),
386 'default' => 0,
387 );
388
389 array_push( $fields['advanced']['fields'], $akismet_tracking );
390 }
391
392 $wp_cron_tracking = array(
393 'name' => 'wp_cron_tracking',
394 'title' => esc_html__( 'WP Cron Tracking', 'stream' ),
395 'type' => 'checkbox',
396 'desc' => esc_html__( 'By default, Stream does not track activity performed by WordPress cron events unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'stream' ),
397 'after_field' => esc_html__( 'Enabled', 'stream' ),
398 'default' => 0,
399 );
400
401 array_push( $fields['advanced']['fields'], $wp_cron_tracking );
402
403 /**
404 * Filter allows for modification of options fields
405 *
406 * @return array Array of option fields
407 */
408 $this->fields = apply_filters( 'wp_stream_settings_option_fields', $fields );
409
410 // Sort option fields in each tab by title ASC.
411 foreach ( $this->fields as $tab => $options ) {
412 $titles = array();
413
414 foreach ( $options['fields'] as $field ) {
415 $prefix = null;
416
417 if ( ! empty( $field['sticky'] ) ) {
418 $prefix = ( 'bottom' === $field['sticky'] ) ? 'ZZZ' : 'AAA';
419 }
420
421 $titles[] = $prefix . $field['title'];
422 }
423
424 array_multisort( $titles, SORT_ASC, $this->fields[ $tab ]['fields'] );
425 }
426
427 return $this->fields;
428 }
429
430 /**
431 * Returns a list of options based on the current screen.
432 *
433 * @return array
434 */
435 public function get_options() {
436 $option_key = $this->option_key;
437 $defaults = $this->get_defaults( $option_key );
438
439 /**
440 * Filter allows for modification of options
441 *
442 * @param array
443 *
444 * @return array Updated array of options
445 */
446 return apply_filters(
447 'wp_stream_settings_options',
448 wp_parse_args(
449 is_network_admin() ? (array) get_site_option( $option_key, array() ) : (array) get_option( $option_key, array() ),
450 $defaults
451 ),
452 $option_key
453 );
454 }
455
456 /**
457 * Iterate through registered fields and extract default values
458 *
459 * @return array
460 */
461 public function get_defaults() {
462 $fields = $this->get_fields();
463 $defaults = array();
464
465 foreach ( $fields as $section_name => $section ) {
466 foreach ( $section['fields'] as $field ) {
467 $defaults[ $section_name . '_' . $field['name'] ] = isset( $field['default'] ) ? $field['default'] : null;
468 }
469 }
470
471 return (array) $defaults;
472 }
473
474 /**
475 * Registers settings fields and sections
476 *
477 * @return void
478 */
479 public function register_settings() {
480 $sections = $this->get_fields();
481
482 register_setting(
483 $this->option_key,
484 $this->option_key,
485 array(
486 $this,
487 'sanitize_settings',
488 )
489 );
490
491 foreach ( $sections as $section_name => $section ) {
492 add_settings_section(
493 $section_name,
494 null,
495 '__return_false',
496 $this->option_key
497 );
498
499 foreach ( $section['fields'] as $field_idx => $field ) {
500 // No field type associated, skip, no GUI.
501 if ( ! isset( $field['type'] ) ) {
502 continue;
503 }
504
505 add_settings_field(
506 $field['name'],
507 $field['title'],
508 ( isset( $field['callback'] ) ? $field['callback'] : array(
509 $this,
510 'output_field',
511 ) ),
512 $this->option_key,
513 $section_name,
514 $field + array(
515 'section' => $section_name,
516 'label_for' => sprintf( '%s_%s_%s', $this->option_key, $section_name, $field['name'] ),
517 )
518 );
519 }
520 }
521 }
522
523 /**
524 * Sanitization callback for settings field values before save
525 *
526 * @param array $input Raw input.
527 *
528 * @return array
529 */
530 public function sanitize_settings( $input ) {
531 $output = array();
532 $sections = $this->get_fields();
533
534 foreach ( $sections as $section => $data ) {
535 if ( empty( $data['fields'] ) || ! is_array( $data['fields'] ) ) {
536 continue;
537 }
538
539 foreach ( $data['fields'] as $field ) {
540 $type = ! empty( $field['type'] ) ? $field['type'] : null;
541 $name = ! empty( $field['name'] ) ? sprintf( '%s_%s', $section, $field['name'] ) : null;
542
543 if ( empty( $type ) || ! isset( $input[ $name ] ) || '' === $input[ $name ] ) {
544 continue;
545 }
546
547 $output[ $name ] = $this->sanitize_setting_by_field_type( $input[ $name ], $type );
548 }
549 }
550
551 return $output;
552 }
553
554 /**
555 * Sanitizes a setting value based on the field type.
556 *
557 * @param mixed $value The value to be sanitized.
558 * @param string $field_type The type of field.
559 *
560 * @return mixed The sanitized value.
561 */
562 public function sanitize_setting_by_field_type( $value, $field_type ) {
563
564 // Sanitize depending on the type of field.
565 switch ( $field_type ) {
566 case 'number':
567 $sanitized_value = is_numeric( $value ) ? intval( trim( $value ) ) : '';
568 break;
569 case 'checkbox':
570 $sanitized_value = is_numeric( $value ) ? absint( trim( $value ) ) : '';
571 break;
572 default:
573 if ( is_array( $value ) ) {
574 $sanitized_value = $value;
575
576 // Support all values in multidimentional arrays too.
577 array_walk_recursive(
578 $sanitized_value,
579 function ( &$v ) {
580 $v = sanitize_text_field( trim( $v ) );
581 }
582 );
583 } else {
584 $sanitized_value = sanitize_text_field( trim( $value ) );
585 }
586 }
587
588 return $sanitized_value;
589 }
590
591 /**
592 * Compile HTML needed for displaying the field
593 *
594 * @param array $field Field settings.
595 *
596 * @return string HTML to be displayed
597 */
598 public function render_field( $field ) {
599 $output = null;
600 $type = isset( $field['type'] ) ? $field['type'] : null;
601 $section = isset( $field['section'] ) ? $field['section'] : null;
602 $name = isset( $field['name'] ) ? $field['name'] : null;
603 $class = isset( $field['class'] ) ? $field['class'] : null;
604 $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : null;
605 $description = isset( $field['desc'] ) ? $field['desc'] : null;
606 $href = isset( $field['href'] ) ? $field['href'] : null;
607 $rows = isset( $field['rows'] ) ? $field['rows'] : 10;
608 $cols = isset( $field['cols'] ) ? $field['cols'] : 50;
609 $after_field = isset( $field['after_field'] ) ? $field['after_field'] : null;
610 $default = isset( $field['default'] ) ? $field['default'] : null;
611 $min = isset( $field['min'] ) ? $field['min'] : 0;
612 $max = isset( $field['max'] ) ? $field['max'] : 999;
613 $step = isset( $field['step'] ) ? $field['step'] : 1;
614 $title = isset( $field['title'] ) ? $field['title'] : null;
615 $nonce = isset( $field['nonce'] ) ? $field['nonce'] : null;
616
617 if ( isset( $field['value'] ) ) {
618 $current_value = $field['value'];
619 } elseif ( isset( $this->options[ $section . '_' . $name ] ) ) {
620 $current_value = $this->options[ $section . '_' . $name ];
621 } else {
622 $current_value = null;
623 }
624
625 $option_key = $this->option_key;
626
627 if ( is_callable( $current_value ) ) {
628 $current_value = call_user_func( $current_value );
629 }
630
631 if ( ! $type || ! $section || ! $name ) {
632 return '';
633 }
634
635 if ( 'multi_checkbox' === $type && ( empty( $field['choices'] ) || ! is_array( $field['choices'] ) ) ) {
636 return '';
637 }
638
639 switch ( $type ) {
640 case 'text':
641 case 'number':
642 $output = sprintf(
643 '<input type="%1$s" name="%2$s[%3$s_%4$s]" id="%2$s_%3$s_%4$s" class="%5$s" placeholder="%6$s" min="%7$d" max="%8$d" step="%9$d" value="%10$s" /> %11$s',
644 esc_attr( $type ),
645 esc_attr( $option_key ),
646 esc_attr( $section ),
647 esc_attr( $name ),
648 esc_attr( $class ),
649 esc_attr( $placeholder ),
650 esc_attr( $min ),
651 esc_attr( $max ),
652 esc_attr( $step ),
653 esc_attr( $current_value ),
654 wp_kses_post( $after_field )
655 );
656 break;
657 case 'textarea':
658 $output = sprintf(
659 '<textarea name="%1$s[%2$s_%3$s]" id="%1$s_%2$s_%3$s" class="%4$s" placeholder="%5$s" rows="%6$d" cols="%7$d">%8$s</textarea> %9$s',
660 esc_attr( $option_key ),
661 esc_attr( $section ),
662 esc_attr( $name ),
663 esc_attr( $class ),
664 esc_attr( $placeholder ),
665 absint( $rows ),
666 absint( $cols ),
667 esc_textarea( $current_value ),
668 wp_kses_post( $after_field )
669 );
670 break;
671 case 'checkbox':
672 if ( isset( $current_value ) ) {
673 $value = $current_value;
674 } elseif ( isset( $default ) ) {
675 $value = $default;
676 } else {
677 $value = 0;
678 }
679
680 $output = sprintf(
681 '<label><input type="checkbox" name="%1$s[%2$s_%3$s]" id="%1$s[%2$s_%3$s]" value="1" %4$s /> %5$s</label>',
682 esc_attr( $option_key ),
683 esc_attr( $section ),
684 esc_attr( $name ),
685 checked( $value, 1, false ),
686 wp_kses_post( $after_field )
687 );
688 break;
689 case 'multi_checkbox':
690 $output = sprintf(
691 '<div id="%1$s[%2$s_%3$s]"><fieldset>',
692 esc_attr( $option_key ),
693 esc_attr( $section ),
694 esc_attr( $name )
695 );
696 // Fallback if nothing is selected.
697 $output .= sprintf(
698 '<input type="hidden" name="%1$s[%2$s_%3$s][]" value="__placeholder__" />',
699 esc_attr( $option_key ),
700 esc_attr( $section ),
701 esc_attr( $name )
702 );
703 $current_value = (array) $current_value;
704 $choices = $field['choices'];
705 if ( is_callable( $choices ) ) {
706 $choices = call_user_func( $choices );
707 }
708 foreach ( $choices as $value => $label ) {
709 $output .= sprintf(
710 '<label>%1$s <span>%2$s</span></label><br />',
711 sprintf(
712 '<input type="checkbox" name="%1$s[%2$s_%3$s][]" value="%4$s" %5$s />',
713 esc_attr( $option_key ),
714 esc_attr( $section ),
715 esc_attr( $name ),
716 esc_attr( $value ),
717 checked( in_array( $value, $current_value, true ), true, false )
718 ),
719 esc_html( $label )
720 );
721 }
722 $output .= '</fieldset></div>';
723 break;
724 case 'select':
725 $current_value = $this->options[ $section . '_' . $name ];
726 $default_value = isset( $default['value'] ) ? $default['value'] : '-1';
727 $default_name = isset( $default['name'] ) ? $default['name'] : 'Choose Setting';
728
729 $output = sprintf(
730 '<select name="%1$s[%2$s_%3$s]" class="%1$s_%2$s_%3$s">',
731 esc_attr( $option_key ),
732 esc_attr( $section ),
733 esc_attr( $name )
734 );
735 $output .= sprintf(
736 '<option value="%1$s" %2$s>%3$s</option>',
737 esc_attr( $default_value ),
738 checked( $default_value === $current_value, true, false ),
739 esc_html( $default_name )
740 );
741 foreach ( $field['choices'] as $value => $label ) {
742 $output .= sprintf(
743 '<option value="%1$s" %2$s>%3$s</option>',
744 esc_attr( $value ),
745 checked( $value === $current_value, true, false ),
746 esc_html( $label )
747 );
748 }
749 $output .= '</select>';
750 break;
751 case 'file':
752 $output = sprintf(
753 '<input type="file" name="%1$s[%2$s_%3$s]" class="%4$s">',
754 esc_attr( $option_key ),
755 esc_attr( $section ),
756 esc_attr( $name ),
757 esc_attr( $class )
758 );
759 break;
760 case 'link':
761 $output = sprintf(
762 '<a id="%1$s_%2$s_%3$s" class="%4$s" href="%5$s">%6$s</a>',
763 esc_attr( $option_key ),
764 esc_attr( $section ),
765 esc_attr( $name ),
766 esc_attr( $class ),
767 esc_attr( $href ),
768 esc_attr( $title )
769 );
770 break;
771 case 'select2':
772 if ( ! isset( $current_value ) ) {
773 $current_value = '';
774 }
775
776 $data_values = array();
777
778 if ( isset( $field['choices'] ) ) {
779 $choices = $field['choices'];
780 if ( is_callable( $choices ) ) {
781 $param = ( isset( $field['param'] ) ) ? $field['param'] : null;
782 $choices = call_user_func( $choices, $param );
783 }
784 foreach ( $choices as $key => $value ) {
785 if ( is_array( $value ) ) {
786 $child_values = array();
787 if ( isset( $value['children'] ) ) {
788 $child_values = array();
789 foreach ( $value['children'] as $child_key => $child_value ) {
790 $child_values[] = array(
791 'id' => $child_key,
792 'text' => $child_value,
793 );
794 }
795 }
796 if ( isset( $value['label'] ) ) {
797 $data_values[] = array(
798 'id' => $key,
799 'text' => $value['label'],
800 'children' => $child_values,
801 );
802 }
803 } else {
804 $data_values[] = array(
805 'id' => $key,
806 'text' => $value,
807 );
808 }
809 }
810 $class .= ' with-source';
811 }
812
813 $input_html = sprintf(
814 '<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" />',
815 esc_attr( $option_key ),
816 esc_attr( $section ),
817 esc_attr( $name ),
818 esc_attr( wp_json_encode( $data_values ) ),
819 esc_attr( $current_value ),
820 esc_attr( $class ),
821 /* translators: %s: the title of the dropdown menu (e.g. "users") */
822 sprintf( esc_html__( 'Any %s', 'stream' ), $title )
823 );
824
825 $output = sprintf(
826 '<div class="%1$s_%2$s_%3$s">%4$s</div>',
827 esc_attr( $option_key ),
828 esc_attr( $section ),
829 esc_attr( $name ),
830 $input_html
831 );
832
833 break;
834 case 'rule_list':
835 $users = count_users();
836 $form = new Form_Generator();
837 $output = '<p class="description">' . esc_html( $description ) . '</p>';
838
839 $actions_top = sprintf( '<input type="button" class="button" id="%1$s_new_rule" value="&#43; %2$s" />', esc_attr( $section . '_' . $name ), esc_html__( 'Add New Rule', 'stream' ) );
840 $actions_bottom = sprintf( '<input type="button" class="button" id="%1$s_remove_rules" value="%2$s" />', esc_attr( $section . '_' . $name ), esc_html__( 'Delete Selected Rules', 'stream' ) );
841
842 $output .= sprintf( '<div class="tablenav top">%1$s</div>', $actions_top );
843 $output .= '<table class="wp-list-table widefat fixed stream-exclude-list">';
844
845 unset( $description );
846
847 $heading_row = sprintf(
848 '<tr>
849 <td scope="col" class="manage-column column-cb check-column">%1$s</td>
850 <th scope="col" class="manage-column">%2$s</th>
851 <th scope="col" class="manage-column">%3$s</th>
852 <th scope="col" class="manage-column">%4$s</th>
853 <th scope="col" class="manage-column">%5$s</th>
854 <th scope="col" class="actions-column manage-column"><span class="hidden">%6$s</span></th>
855 </tr>',
856 '<input class="cb-select" type="checkbox" />',
857 esc_html__( 'Author or Role', 'stream' ),
858 esc_html__( 'Context', 'stream' ),
859 esc_html__( 'Action', 'stream' ),
860 esc_html__( 'IP Address', 'stream' ),
861 esc_html__( 'Filters', 'stream' )
862 );
863
864 $exclude_rows = array();
865
866 // Account for when no rules have been added yet.
867 if ( ! is_array( $current_value ) ) {
868 $current_value = array();
869 }
870
871 // Prepend an empty row.
872 $current_value['exclude_row'] = ( isset( $current_value['exclude_row'] ) ? $current_value['exclude_row'] : array() ) + array( 'helper' => '' );
873
874 foreach ( $current_value['exclude_row'] as $key => $value ) {
875 // Prepare values.
876 $author_or_role = isset( $current_value['author_or_role'][ $key ] ) ? $current_value['author_or_role'][ $key ] : '';
877 $connector = isset( $current_value['connector'][ $key ] ) ? $current_value['connector'][ $key ] : '';
878 $context = isset( $current_value['context'][ $key ] ) ? $current_value['context'][ $key ] : '';
879 $action = isset( $current_value['action'][ $key ] ) ? $current_value['action'][ $key ] : '';
880 $ip_address = isset( $current_value['ip_address'][ $key ] ) ? $current_value['ip_address'][ $key ] : '';
881
882 // Author or Role dropdown menu.
883 $author_or_role_values = array();
884 $author_or_role_selected = array();
885
886 foreach ( $this->get_roles() as $role_id => $role ) {
887 $args = array(
888 'value' => $role_id,
889 'text' => $role,
890 );
891 $count = isset( $users['avail_roles'][ $role_id ] ) ? $users['avail_roles'][ $role_id ] : 0;
892
893 if ( ! empty( $count ) ) {
894 /* translators: %d: a number of users (e.g. "42") */
895 $args['user_count'] = sprintf( _n( '%d user', '%d users', absint( $count ), 'stream' ), absint( $count ) );
896 }
897
898 if ( $role_id === $author_or_role ) {
899 $author_or_role_selected['value'] = $role_id;
900 $author_or_role_selected['text'] = $role;
901 }
902
903 $author_or_role_values[] = $args;
904 }
905
906 if ( empty( $author_or_role_selected ) && is_numeric( $author_or_role ) ) {
907 $user = new WP_User( $author_or_role );
908 $display_name = ( 0 === $user->ID ) ? esc_html__( 'N/A', 'stream' ) : $user->display_name;
909 $author_or_role_selected = array(
910 'value' => $user->ID,
911 'text' => $display_name,
912 );
913 $author_or_role_values[] = $author_or_role_selected;
914 }
915
916 $author_or_role_input = $form->render_field(
917 'select2',
918 array(
919 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'author_or_role' ) ),
920 'options' => $author_or_role_values,
921 'classes' => 'author_or_role',
922 // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
923 'data' => array(
924 'placeholder' => __( 'Any Author or Role', 'stream' ),
925 'nonce' => wp_create_nonce( 'stream_get_users' ),
926 'selected-id' => isset( $author_or_role_selected['value'] ) ? $author_or_role_selected['value'] : '',
927 'selected-text' => isset( $author_or_role_selected['text'] ) ? $author_or_role_selected['text'] : '',
928 ),
929 ),
930 false
931 );
932
933 // Context dropdown menu.
934 $context_values = array();
935
936 foreach ( $this->get_terms_labels( 'context' ) as $context_id => $context_data ) {
937 if ( is_array( $context_data ) ) {
938 $child_values = array();
939 if ( isset( $context_data['children'] ) ) {
940 $child_values = array();
941 foreach ( $context_data['children'] as $child_id => $child_value ) {
942 $child_values[] = array(
943 'value' => $context_id . '-' . $child_id,
944 'text' => $child_value,
945 'parent' => $context_id,
946 );
947 }
948 }
949 if ( isset( $context_data['label'] ) ) {
950 $context_values[] = array(
951 'value' => $context_id,
952 'text' => $context_data['label'],
953 'children' => $child_values,
954 );
955 }
956 } else {
957 $context_values[] = array(
958 'value' => $context_id,
959 'text' => $context_data,
960 );
961 }
962 }
963
964 $connector_or_context_input = $form->render_field(
965 'select2',
966 array(
967 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector_or_context' ) ),
968 'options' => $context_values,
969 'classes' => 'connector_or_context',
970 // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
971 'data' => array(
972 'group' => 'connector',
973 'placeholder' => __( 'Any Context', 'stream' ),
974 ),
975 ),
976 false
977 );
978
979 $connector_input = $form->render_field(
980 'hidden',
981 array(
982 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector' ) ),
983 'value' => $connector,
984 'classes' => 'connector',
985 ),
986 false
987 );
988
989 $context_input = $form->render_field(
990 'hidden',
991 array(
992 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'context' ) ),
993 'value' => $context,
994 'classes' => 'context',
995 ),
996 false
997 );
998
999 // Action dropdown menu.
1000 $action_values = array();
1001
1002 foreach ( $this->get_terms_labels( 'action' ) as $action_id => $action_data ) {
1003 $action_values[] = array(
1004 'value' => $action_id,
1005 'text' => $action_data,
1006 );
1007 }
1008
1009 $action_input = $form->render_field(
1010 'select2',
1011 array(
1012 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'action' ) ),
1013 'value' => $action,
1014 'options' => $action_values,
1015 'classes' => 'action',
1016 // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
1017 'data' => array(
1018 'placeholder' => __( 'Any Action', 'stream' ),
1019 ),
1020 ),
1021 false
1022 );
1023
1024 // IP Address input.
1025 $ip_address_input = $form->render_field(
1026 'select2',
1027 array(
1028 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ),
1029 'value' => $ip_address,
1030 'classes' => 'ip_address',
1031 // Data attributes are escaped in Form_Generator::prepare_data_attributes_string().
1032 'data' => array(
1033 'placeholder' => __( 'Any IP Address', 'stream' ),
1034 'nonce' => wp_create_nonce( 'stream_get_ips' ),
1035 ),
1036 'multiple' => true,
1037 ),
1038 false
1039 );
1040
1041 // Hidden helper input.
1042 $helper_input = sprintf(
1043 '<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" value="" />',
1044 esc_attr( $option_key ),
1045 esc_attr( $section ),
1046 esc_attr( $name ),
1047 'exclude_row'
1048 );
1049
1050 $exclude_rows[] = sprintf(
1051 '<tr class="%1$s %2$s">
1052 <th scope="row" class="check-column">%3$s %4$s</th>
1053 <td>%5$s</td>
1054 <td>%6$s %7$s %8$s</td>
1055 <td>%9$s</td>
1056 <td>%10$s</td>
1057 <th scope="row" class="actions-column">
1058 <a href="#" class="exclude_rules_remove_rule_row">%11$s</a>
1059 </th>
1060 </tr>',
1061 ( 0 !== (int) $key % 2 ) ? 'alternate' : '',
1062 ( 'helper' === (string) $key ) ? 'hidden helper' : '',
1063 '<input class="cb-select" type="checkbox" />',
1064 $helper_input,
1065 $author_or_role_input,
1066 $connector_or_context_input,
1067 $connector_input,
1068 $context_input,
1069 $action_input,
1070 $ip_address_input,
1071 esc_html__( 'Delete', 'stream' )
1072 );
1073 }
1074
1075 $no_rules_found_row = sprintf(
1076 '<tr class="no-items hidden"><td class="colspanchange" colspan="5">%1$s</td></tr>',
1077 esc_html__( 'No rules found.', 'stream' )
1078 );
1079
1080 $output .= '<thead>' . $heading_row . '</thead>';
1081 $output .= '<tfoot>' . $heading_row . '</tfoot>';
1082 $output .= '<tbody>' . $no_rules_found_row . implode( '', $exclude_rows ) . '</tbody>';
1083
1084 $output .= '</table>';
1085
1086 $output .= sprintf( '<div class="tablenav bottom">%1$s</div>', $actions_bottom );
1087
1088 break;
1089 }
1090 $output .= ! empty( $description ) ? wp_kses_post( sprintf( '<p class="description">%s</p>', $description ) ) : null;
1091
1092 return $output;
1093 }
1094
1095 /**
1096 * Render Callback for post_types field
1097 *
1098 * @param array $field Field to be rendered.
1099 *
1100 * @return string
1101 */
1102 public function output_field( $field ) {
1103 $method = 'output_' . $field['name'];
1104
1105 if ( method_exists( $this, $method ) ) {
1106 return call_user_func( array( $this, $method ), $field );
1107 }
1108
1109 $output = $this->render_field( $field );
1110
1111 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1112 }
1113
1114 /**
1115 * Get an array of user roles
1116 *
1117 * @return array
1118 */
1119 public function get_roles() {
1120 $wp_roles = new WP_Roles();
1121 $roles = array();
1122
1123 foreach ( $wp_roles->get_names() as $role => $label ) {
1124 $roles[ $role ] = translate_user_role( $label );
1125 }
1126
1127 return $roles;
1128 }
1129
1130 /**
1131 * Function will return all terms labels of given column
1132 *
1133 * @param string $column Name of the column.
1134 *
1135 * @return array
1136 */
1137 public function get_terms_labels( $column ) {
1138 $return_labels = array();
1139
1140 if ( isset( $this->plugin->connectors->term_labels[ 'stream_' . $column ] ) ) {
1141 if ( 'context' === $column && isset( $this->plugin->connectors->term_labels['stream_connector'] ) ) {
1142 $connectors = $this->plugin->connectors->term_labels['stream_connector'];
1143 $contexts = $this->plugin->connectors->term_labels['stream_context'];
1144
1145 foreach ( $connectors as $connector => $connector_label ) {
1146 $return_labels[ $connector ]['label'] = $connector_label;
1147 foreach ( $contexts as $context => $context_label ) {
1148 if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context, $this->plugin->connectors->contexts[ $connector ] ) ) {
1149 $return_labels[ $connector ]['children'][ $context ] = $context_label;
1150 }
1151 }
1152 }
1153 } else {
1154 $return_labels = $this->plugin->connectors->term_labels[ 'stream_' . $column ];
1155 }
1156
1157 ksort( $return_labels );
1158 }
1159
1160 return $return_labels;
1161 }
1162
1163 /**
1164 * Remove records when records TTL is shortened
1165 *
1166 * @action update_option_wp_stream
1167 *
1168 * @param array $old_value Old value.
1169 * @param array $new_value New value.
1170 */
1171 public function updated_option_ttl_remove_records( $old_value, $new_value ) {
1172 $ttl_before = isset( $old_value['general_records_ttl'] ) ? (int) $old_value['general_records_ttl'] : - 1;
1173 $ttl_after = isset( $new_value['general_records_ttl'] ) ? (int) $new_value['general_records_ttl'] : - 1;
1174
1175 if ( $ttl_after < $ttl_before ) {
1176 /**
1177 * Action assists in purging when TTL is shortened
1178 */
1179 do_action( 'wp_stream_auto_purge' );
1180 }
1181 }
1182
1183 /**
1184 * Get translations of serialized Stream settings
1185 *
1186 * @filter wp_stream_serialized_labels
1187 *
1188 * @param array $labels Setting labels.
1189 *
1190 * @return array Multidimensional array of fields
1191 */
1192 public function get_settings_translations( $labels ) {
1193 if ( ! isset( $labels[ $this->option_key ] ) ) {
1194 $labels[ $this->option_key ] = array();
1195 }
1196
1197 foreach ( $this->get_fields() as $section_slug => $section ) {
1198 foreach ( $section['fields'] as $field ) {
1199 $labels[ $this->option_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
1200 }
1201 }
1202
1203 return $labels;
1204 }
1205 }
1206