PluginProbe
Stream – Activity Log & Audit Trail / 3.9.2
Stream – Activity Log & Audit Trail v3.9.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.9.2, at classes/class-settings.php

1,182 lines 34.1 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 // xss ok.
518 )
519 );
520 }
521 }
522 }
523
524 /**
525 * Sanitization callback for settings field values before save
526 *
527 * @param array $input Raw input.
528 *
529 * @return array
530 */
531 public function sanitize_settings( $input ) {
532 $output = array();
533 $sections = $this->get_fields();
534
535 foreach ( $sections as $section => $data ) {
536 if ( empty( $data['fields'] ) || ! is_array( $data['fields'] ) ) {
537 continue;
538 }
539
540 foreach ( $data['fields'] as $field ) {
541 $type = ! empty( $field['type'] ) ? $field['type'] : null;
542 $name = ! empty( $field['name'] ) ? sprintf( '%s_%s', $section, $field['name'] ) : null;
543
544 if ( empty( $type ) || ! isset( $input[ $name ] ) || '' === $input[ $name ] ) {
545 continue;
546 }
547
548 // Sanitize depending on the type of field.
549 switch ( $type ) {
550 case 'number':
551 $output[ $name ] = is_numeric( $input[ $name ] ) ? intval( trim( $input[ $name ] ) ) : '';
552 break;
553 case 'checkbox':
554 $output[ $name ] = is_numeric( $input[ $name ] ) ? absint( trim( $input[ $name ] ) ) : '';
555 break;
556 default:
557 if ( is_array( $input[ $name ] ) ) {
558 $output[ $name ] = $input[ $name ];
559
560 // Support all values in multidimentional arrays too.
561 array_walk_recursive(
562 $output[ $name ],
563 function ( &$v ) {
564 $v = sanitize_text_field( trim( $v ) );
565 }
566 );
567 } else {
568 $output[ $name ] = sanitize_text_field( trim( $input[ $name ] ) );
569 }
570 }
571 }
572 }
573
574 return $output;
575 }
576
577 /**
578 * Compile HTML needed for displaying the field
579 *
580 * @param array $field Field settings.
581 *
582 * @return string HTML to be displayed
583 */
584 public function render_field( $field ) {
585 $output = null;
586 $type = isset( $field['type'] ) ? $field['type'] : null;
587 $section = isset( $field['section'] ) ? $field['section'] : null;
588 $name = isset( $field['name'] ) ? $field['name'] : null;
589 $class = isset( $field['class'] ) ? $field['class'] : null;
590 $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : null;
591 $description = isset( $field['desc'] ) ? $field['desc'] : null;
592 $href = isset( $field['href'] ) ? $field['href'] : null;
593 $rows = isset( $field['rows'] ) ? $field['rows'] : 10;
594 $cols = isset( $field['cols'] ) ? $field['cols'] : 50;
595 $after_field = isset( $field['after_field'] ) ? $field['after_field'] : null;
596 $default = isset( $field['default'] ) ? $field['default'] : null;
597 $min = isset( $field['min'] ) ? $field['min'] : 0;
598 $max = isset( $field['max'] ) ? $field['max'] : 999;
599 $step = isset( $field['step'] ) ? $field['step'] : 1;
600 $title = isset( $field['title'] ) ? $field['title'] : null;
601 $nonce = isset( $field['nonce'] ) ? $field['nonce'] : null;
602
603 if ( isset( $field['value'] ) ) {
604 $current_value = $field['value'];
605 } else {
606 if ( isset( $this->options[ $section . '_' . $name ] ) ) {
607 $current_value = $this->options[ $section . '_' . $name ];
608 } else {
609 $current_value = null;
610 }
611 }
612
613 $option_key = $this->option_key;
614
615 if ( is_callable( $current_value ) ) {
616 $current_value = call_user_func( $current_value );
617 }
618
619 if ( ! $type || ! $section || ! $name ) {
620 return '';
621 }
622
623 if ( 'multi_checkbox' === $type && ( empty( $field['choices'] ) || ! is_array( $field['choices'] ) ) ) {
624 return '';
625 }
626
627 switch ( $type ) {
628 case 'text':
629 case 'number':
630 $output = sprintf(
631 '<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',
632 esc_attr( $type ),
633 esc_attr( $option_key ),
634 esc_attr( $section ),
635 esc_attr( $name ),
636 esc_attr( $class ),
637 esc_attr( $placeholder ),
638 esc_attr( $min ),
639 esc_attr( $max ),
640 esc_attr( $step ),
641 esc_attr( $current_value ),
642 wp_kses_post( $after_field )
643 );
644 break;
645 case 'textarea':
646 $output = sprintf(
647 '<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',
648 esc_attr( $option_key ),
649 esc_attr( $section ),
650 esc_attr( $name ),
651 esc_attr( $class ),
652 esc_attr( $placeholder ),
653 absint( $rows ),
654 absint( $cols ),
655 esc_textarea( $current_value ),
656 wp_kses_post( $after_field )
657 );
658 break;
659 case 'checkbox':
660 if ( isset( $current_value ) ) {
661 $value = $current_value;
662 } elseif ( isset( $default ) ) {
663 $value = $default;
664 } else {
665 $value = 0;
666 }
667
668 $output = sprintf(
669 '<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>',
670 esc_attr( $option_key ),
671 esc_attr( $section ),
672 esc_attr( $name ),
673 checked( $value, 1, false ),
674 wp_kses_post( $after_field )
675 );
676 break;
677 case 'multi_checkbox':
678 $output = sprintf(
679 '<div id="%1$s[%2$s_%3$s]"><fieldset>',
680 esc_attr( $option_key ),
681 esc_attr( $section ),
682 esc_attr( $name )
683 );
684 // Fallback if nothing is selected.
685 $output .= sprintf(
686 '<input type="hidden" name="%1$s[%2$s_%3$s][]" value="__placeholder__" />',
687 esc_attr( $option_key ),
688 esc_attr( $section ),
689 esc_attr( $name )
690 );
691 $current_value = (array) $current_value;
692 $choices = $field['choices'];
693 if ( is_callable( $choices ) ) {
694 $choices = call_user_func( $choices );
695 }
696 foreach ( $choices as $value => $label ) {
697 $output .= sprintf(
698 '<label>%1$s <span>%2$s</span></label><br />',
699 sprintf(
700 '<input type="checkbox" name="%1$s[%2$s_%3$s][]" value="%4$s" %5$s />',
701 esc_attr( $option_key ),
702 esc_attr( $section ),
703 esc_attr( $name ),
704 esc_attr( $value ),
705 checked( in_array( $value, $current_value, true ), true, false )
706 ),
707 esc_html( $label )
708 );
709 }
710 $output .= '</fieldset></div>';
711 break;
712 case 'select':
713 $current_value = $this->options[ $section . '_' . $name ];
714 $default_value = isset( $default['value'] ) ? $default['value'] : '-1';
715 $default_name = isset( $default['name'] ) ? $default['name'] : 'Choose Setting';
716
717 $output = sprintf(
718 '<select name="%1$s[%2$s_%3$s]" class="%1$s_%2$s_%3$s">',
719 esc_attr( $option_key ),
720 esc_attr( $section ),
721 esc_attr( $name )
722 );
723 $output .= sprintf(
724 '<option value="%1$s" %2$s>%3$s</option>',
725 esc_attr( $default_value ),
726 checked( $default_value === $current_value, true, false ),
727 esc_html( $default_name )
728 );
729 foreach ( $field['choices'] as $value => $label ) {
730 $output .= sprintf(
731 '<option value="%1$s" %2$s>%3$s</option>',
732 esc_attr( $value ),
733 checked( $value === $current_value, true, false ),
734 esc_html( $label )
735 );
736 }
737 $output .= '</select>';
738 break;
739 case 'file':
740 $output = sprintf(
741 '<input type="file" name="%1$s[%2$s_%3$s]" class="%4$s">',
742 esc_attr( $option_key ),
743 esc_attr( $section ),
744 esc_attr( $name ),
745 esc_attr( $class )
746 );
747 break;
748 case 'link':
749 $output = sprintf(
750 '<a id="%1$s_%2$s_%3$s" class="%4$s" href="%5$s">%6$s</a>',
751 esc_attr( $option_key ),
752 esc_attr( $section ),
753 esc_attr( $name ),
754 esc_attr( $class ),
755 esc_attr( $href ),
756 esc_attr( $title )
757 );
758 break;
759 case 'select2':
760 if ( ! isset( $current_value ) ) {
761 $current_value = '';
762 }
763
764 $data_values = array();
765
766 if ( isset( $field['choices'] ) ) {
767 $choices = $field['choices'];
768 if ( is_callable( $choices ) ) {
769 $param = ( isset( $field['param'] ) ) ? $field['param'] : null;
770 $choices = call_user_func( $choices, $param );
771 }
772 foreach ( $choices as $key => $value ) {
773 if ( is_array( $value ) ) {
774 $child_values = array();
775 if ( isset( $value['children'] ) ) {
776 $child_values = array();
777 foreach ( $value['children'] as $child_key => $child_value ) {
778 $child_values[] = array(
779 'id' => $child_key,
780 'text' => $child_value,
781 );
782 }
783 }
784 if ( isset( $value['label'] ) ) {
785 $data_values[] = array(
786 'id' => $key,
787 'text' => $value['label'],
788 'children' => $child_values,
789 );
790 }
791 } else {
792 $data_values[] = array(
793 'id' => $key,
794 'text' => $value,
795 );
796 }
797 }
798 $class .= ' with-source';
799 }
800
801 $input_html = sprintf(
802 '<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" />',
803 esc_attr( $option_key ),
804 esc_attr( $section ),
805 esc_attr( $name ),
806 esc_attr( wp_stream_json_encode( $data_values ) ),
807 esc_attr( $current_value ),
808 esc_attr( $class ),
809 /* translators: %s: the title of the dropdown menu (e.g. "users") */
810 sprintf( esc_html__( 'Any %s', 'stream' ), $title )
811 );
812
813 $output = sprintf(
814 '<div class="%1$s_%2$s_%3$s">%4$s</div>',
815 esc_attr( $option_key ),
816 esc_attr( $section ),
817 esc_attr( $name ),
818 $input_html
819 );
820
821 break;
822 case 'rule_list':
823 $users = count_users();
824 $form = new Form_Generator();
825 $output = '<p class="description">' . esc_html( $description ) . '</p>';
826
827 $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' ) );
828 $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' ) );
829
830 $output .= sprintf( '<div class="tablenav top">%1$s</div>', $actions_top );
831 $output .= '<table class="wp-list-table widefat fixed stream-exclude-list">';
832
833 unset( $description );
834
835 $heading_row = sprintf(
836 '<tr>
837 <td scope="col" class="manage-column column-cb check-column">%1$s</td>
838 <th scope="col" class="manage-column">%2$s</th>
839 <th scope="col" class="manage-column">%3$s</th>
840 <th scope="col" class="manage-column">%4$s</th>
841 <th scope="col" class="manage-column">%5$s</th>
842 <th scope="col" class="actions-column manage-column"><span class="hidden">%6$s</span></th>
843 </tr>',
844 '<input class="cb-select" type="checkbox" />',
845 esc_html__( 'Author or Role', 'stream' ),
846 esc_html__( 'Context', 'stream' ),
847 esc_html__( 'Action', 'stream' ),
848 esc_html__( 'IP Address', 'stream' ),
849 esc_html__( 'Filters', 'stream' )
850 );
851
852 $exclude_rows = array();
853
854 // Account for when no rules have been added yet.
855 if ( ! is_array( $current_value ) ) {
856 $current_value = array();
857 }
858
859 // Prepend an empty row.
860 $current_value['exclude_row'] = ( isset( $current_value['exclude_row'] ) ? $current_value['exclude_row'] : array() ) + array( 'helper' => '' );
861
862 foreach ( $current_value['exclude_row'] as $key => $value ) {
863 // Prepare values.
864 $author_or_role = isset( $current_value['author_or_role'][ $key ] ) ? $current_value['author_or_role'][ $key ] : '';
865 $connector = isset( $current_value['connector'][ $key ] ) ? $current_value['connector'][ $key ] : '';
866 $context = isset( $current_value['context'][ $key ] ) ? $current_value['context'][ $key ] : '';
867 $action = isset( $current_value['action'][ $key ] ) ? $current_value['action'][ $key ] : '';
868 $ip_address = isset( $current_value['ip_address'][ $key ] ) ? $current_value['ip_address'][ $key ] : '';
869
870 // Author or Role dropdown menu.
871 $author_or_role_values = array();
872 $author_or_role_selected = array();
873
874 foreach ( $this->get_roles() as $role_id => $role ) {
875 $args = array(
876 'value' => $role_id,
877 'text' => $role,
878 );
879 $count = isset( $users['avail_roles'][ $role_id ] ) ? $users['avail_roles'][ $role_id ] : 0;
880
881 if ( ! empty( $count ) ) {
882 /* translators: %d: a number of users (e.g. "42") */
883 $args['user_count'] = sprintf( _n( '%d user', '%d users', absint( $count ), 'stream' ), absint( $count ) );
884 }
885
886 if ( $role_id === $author_or_role ) {
887 $author_or_role_selected['value'] = $role_id;
888 $author_or_role_selected['text'] = $role;
889 }
890
891 $author_or_role_values[] = $args;
892 }
893
894 if ( empty( $author_or_role_selected ) && is_numeric( $author_or_role ) ) {
895 $user = new WP_User( $author_or_role );
896 $display_name = ( 0 === $user->ID ) ? esc_html__( 'N/A', 'stream' ) : $user->display_name;
897 $author_or_role_selected = array(
898 'value' => $user->ID,
899 'text' => $display_name,
900 );
901 $author_or_role_values[] = $author_or_role_selected;
902 }
903
904 $author_or_role_input = $form->render_field(
905 'select2',
906 array(
907 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'author_or_role' ) ),
908 'options' => $author_or_role_values,
909 'classes' => 'author_or_role',
910 'data' => array(
911 'placeholder' => esc_html__( 'Any Author or Role', 'stream' ),
912 'nonce' => esc_attr( wp_create_nonce( 'stream_get_users' ) ),
913 'selected-id' => isset( $author_or_role_selected['value'] ) ? esc_attr( $author_or_role_selected['value'] ) : '',
914 'selected-text' => isset( $author_or_role_selected['text'] ) ? esc_attr( $author_or_role_selected['text'] ) : '',
915 ),
916 )
917 );
918
919 // Context dropdown menu.
920 $context_values = array();
921
922 foreach ( $this->get_terms_labels( 'context' ) as $context_id => $context_data ) {
923 if ( is_array( $context_data ) ) {
924 $child_values = array();
925 if ( isset( $context_data['children'] ) ) {
926 $child_values = array();
927 foreach ( $context_data['children'] as $child_id => $child_value ) {
928 $child_values[] = array(
929 'value' => $context_id . '-' . $child_id,
930 'text' => $child_value,
931 'parent' => $context_id,
932 );
933 }
934 }
935 if ( isset( $context_data['label'] ) ) {
936 $context_values[] = array(
937 'value' => $context_id,
938 'text' => $context_data['label'],
939 'children' => $child_values,
940 );
941 }
942 } else {
943 $context_values[] = array(
944 'value' => $context_id,
945 'text' => $context_data,
946 );
947 }
948 }
949
950 $connector_or_context_input = $form->render_field(
951 'select2',
952 array(
953 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector_or_context' ) ),
954 'options' => $context_values,
955 'classes' => 'connector_or_context',
956 'data' => array(
957 'group' => 'connector',
958 'placeholder' => __( 'Any Context', 'stream' ),
959 ),
960 )
961 );
962
963 $connector_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, 'connector' ) ),
967 'value' => $connector,
968 'classes' => 'connector',
969 )
970 );
971
972 $context_input = $form->render_field(
973 'hidden',
974 array(
975 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'context' ) ),
976 'value' => $context,
977 'classes' => 'context',
978 )
979 );
980
981 // Action dropdown menu.
982 $action_values = array();
983
984 foreach ( $this->get_terms_labels( 'action' ) as $action_id => $action_data ) {
985 $action_values[] = array(
986 'value' => $action_id,
987 'text' => $action_data,
988 );
989 }
990
991 $action_input = $form->render_field(
992 'select2',
993 array(
994 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'action' ) ),
995 'value' => $action,
996 'options' => $action_values,
997 'classes' => 'action',
998 'data' => array(
999 'placeholder' => __( 'Any Action', 'stream' ),
1000 ),
1001 )
1002 );
1003
1004 // IP Address input.
1005 $ip_address_input = $form->render_field(
1006 'select2',
1007 array(
1008 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ),
1009 'value' => $ip_address,
1010 'classes' => 'ip_address',
1011 'data' => array(
1012 'placeholder' => esc_attr__( 'Any IP Address', 'stream' ),
1013 'nonce' => esc_attr( wp_create_nonce( 'stream_get_ips' ) ),
1014 ),
1015 'multiple' => true,
1016 )
1017 );
1018
1019 // Hidden helper input.
1020 $helper_input = sprintf(
1021 '<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" value="" />',
1022 esc_attr( $option_key ),
1023 esc_attr( $section ),
1024 esc_attr( $name ),
1025 'exclude_row'
1026 );
1027
1028 $exclude_rows[] = sprintf(
1029 '<tr class="%1$s %2$s">
1030 <th scope="row" class="check-column">%3$s %4$s</th>
1031 <td>%5$s</td>
1032 <td>%6$s %7$s %8$s</td>
1033 <td>%9$s</td>
1034 <td>%10$s</td>
1035 <th scope="row" class="actions-column">%11$s</th>
1036 </tr>',
1037 ( 0 !== (int) $key % 2 ) ? 'alternate' : '',
1038 ( 'helper' === (string) $key ) ? 'hidden helper' : '',
1039 '<input class="cb-select" type="checkbox" />',
1040 $helper_input,
1041 $author_or_role_input,
1042 $connector_or_context_input,
1043 $connector_input,
1044 $context_input,
1045 $action_input,
1046 $ip_address_input,
1047 '<a href="#" class="exclude_rules_remove_rule_row">Delete</a>'
1048 );
1049 }
1050
1051 $no_rules_found_row = sprintf(
1052 '<tr class="no-items hidden"><td class="colspanchange" colspan="5">%1$s</td></tr>',
1053 esc_html__( 'No rules found.', 'stream' )
1054 );
1055
1056 $output .= '<thead>' . $heading_row . '</thead>';
1057 $output .= '<tfoot>' . $heading_row . '</tfoot>';
1058 $output .= '<tbody>' . $no_rules_found_row . implode( '', $exclude_rows ) . '</tbody>';
1059
1060 $output .= '</table>';
1061
1062 $output .= sprintf( '<div class="tablenav bottom">%1$s</div>', $actions_bottom );
1063
1064 break;
1065 }
1066 $output .= ! empty( $description ) ? wp_kses_post( sprintf( '<p class="description">%s</p>', $description ) ) : null;
1067
1068 return $output;
1069 }
1070
1071 /**
1072 * Render Callback for post_types field
1073 *
1074 * @param array $field Field to be rendered.
1075 *
1076 * @return string
1077 */
1078 public function output_field( $field ) {
1079 $method = 'output_' . $field['name'];
1080
1081 if ( method_exists( $this, $method ) ) {
1082 return call_user_func( array( $this, $method ), $field );
1083 }
1084
1085 $output = $this->render_field( $field );
1086
1087 echo $output; // xss ok.
1088 }
1089
1090 /**
1091 * Get an array of user roles
1092 *
1093 * @return array
1094 */
1095 public function get_roles() {
1096 $wp_roles = new WP_Roles();
1097 $roles = array();
1098
1099 foreach ( $wp_roles->get_names() as $role => $label ) {
1100 $roles[ $role ] = translate_user_role( $label );
1101 }
1102
1103 return $roles;
1104 }
1105
1106 /**
1107 * Function will return all terms labels of given column
1108 *
1109 * @param string $column Name of the column.
1110 *
1111 * @return array
1112 */
1113 public function get_terms_labels( $column ) {
1114 $return_labels = array();
1115
1116 if ( isset( $this->plugin->connectors->term_labels[ 'stream_' . $column ] ) ) {
1117 if ( 'context' === $column && isset( $this->plugin->connectors->term_labels['stream_connector'] ) ) {
1118 $connectors = $this->plugin->connectors->term_labels['stream_connector'];
1119 $contexts = $this->plugin->connectors->term_labels['stream_context'];
1120
1121 foreach ( $connectors as $connector => $connector_label ) {
1122 $return_labels[ $connector ]['label'] = $connector_label;
1123 foreach ( $contexts as $context => $context_label ) {
1124 if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context, $this->plugin->connectors->contexts[ $connector ] ) ) {
1125 $return_labels[ $connector ]['children'][ $context ] = $context_label;
1126 }
1127 }
1128 }
1129 } else {
1130 $return_labels = $this->plugin->connectors->term_labels[ 'stream_' . $column ];
1131 }
1132
1133 ksort( $return_labels );
1134 }
1135
1136 return $return_labels;
1137 }
1138
1139 /**
1140 * Remove records when records TTL is shortened
1141 *
1142 * @action update_option_wp_stream
1143 *
1144 * @param array $old_value Old value.
1145 * @param array $new_value New value.
1146 */
1147 public function updated_option_ttl_remove_records( $old_value, $new_value ) {
1148 $ttl_before = isset( $old_value['general_records_ttl'] ) ? (int) $old_value['general_records_ttl'] : - 1;
1149 $ttl_after = isset( $new_value['general_records_ttl'] ) ? (int) $new_value['general_records_ttl'] : - 1;
1150
1151 if ( $ttl_after < $ttl_before ) {
1152 /**
1153 * Action assists in purging when TTL is shortened
1154 */
1155 do_action( 'wp_stream_auto_purge' );
1156 }
1157 }
1158
1159 /**
1160 * Get translations of serialized Stream settings
1161 *
1162 * @filter wp_stream_serialized_labels
1163 *
1164 * @param array $labels Setting labels.
1165 *
1166 * @return array Multidimensional array of fields
1167 */
1168 public function get_settings_translations( $labels ) {
1169 if ( ! isset( $labels[ $this->option_key ] ) ) {
1170 $labels[ $this->option_key ] = array();
1171 }
1172
1173 foreach ( $this->get_fields() as $section_slug => $section ) {
1174 foreach ( $section['fields'] as $field ) {
1175 $labels[ $this->option_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
1176 }
1177 }
1178
1179 return $labels;
1180 }
1181 }
1182