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

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