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
stream / classes / class-settings.php

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

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