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