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

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