PluginProbe
Stream – Activity Log & Audit Trail / 3.9.3
Stream – Activity Log & Audit Trail v3.9.3
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-admin.php

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

1,111 lines 28.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Centralized manager for WordPress backend functionality.
4 *
5 * @package WP_Stream
6 */
7
8 namespace WP_Stream;
9
10 use DateTime;
11 use DateTimeZone;
12 use DateInterval;
13 use \WP_CLI;
14 use \WP_Roles;
15
16 /**
17 * Class - Admin
18 */
19 class Admin {
20
21 /**
22 * Holds Instance of plugin object
23 *
24 * @var Plugin
25 */
26 public $plugin;
27
28 /**
29 * Holds Network class
30 *
31 * @var Network
32 */
33 public $network;
34
35 /**
36 * Holds Live Update class
37 *
38 * @var Live_Update
39 */
40 public $live_update;
41
42 /**
43 * Holds Export class
44 *
45 * @var Export
46 */
47 public $export;
48
49 /**
50 * Menu page screen id
51 *
52 * @var string
53 */
54 public $screen_id = array();
55
56 /**
57 * List table object
58 *
59 * @var List_Table
60 */
61 public $list_table = null;
62
63 /**
64 * Option to disable access to Stream
65 *
66 * @var bool
67 */
68 public $disable_access = false;
69
70 /**
71 * Class applied to the body of the admin screen
72 *
73 * @var string
74 */
75 public $admin_body_class = 'wp_stream_screen';
76
77 /**
78 * Slug of the records page
79 *
80 * @var string
81 */
82 public $records_page_slug = 'wp_stream';
83
84 /**
85 * Slug of the settings page
86 *
87 * @var string
88 */
89 public $settings_page_slug = 'wp_stream_settings';
90
91 /**
92 * Parent page of the records and settings pages
93 *
94 * @var string
95 */
96 public $admin_parent_page = 'admin.php';
97
98 /**
99 * Capability name for viewing records
100 *
101 * @var string
102 */
103 public $view_cap = 'view_stream';
104
105 /**
106 * Capability name for viewing settings
107 *
108 * @var string
109 */
110 public $settings_cap = 'manage_options';
111
112 /**
113 * Total amount of authors to pre-load
114 *
115 * @var int
116 */
117 public $preload_users_max = 50;
118
119 /**
120 * Admin notices, collected and displayed on proper action
121 *
122 * @var array
123 */
124 public $notices = array();
125
126 /**
127 * Class constructor.
128 *
129 * @param Plugin $plugin Instance of plugin object.
130 */
131 public function __construct( $plugin ) {
132 $this->plugin = $plugin;
133
134 add_action( 'init', array( $this, 'init' ) );
135
136 // Ensure function used in various methods is pre-loaded.
137 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
138 require_once ABSPATH . '/wp-admin/includes/plugin.php';
139 }
140
141 // User and role caps.
142 add_filter( 'user_has_cap', array( $this, 'filter_user_caps' ), 10, 4 );
143 add_filter( 'role_has_cap', array( $this, 'filter_role_caps' ), 10, 3 );
144
145 if ( is_multisite() && $plugin->is_network_activated() && ! is_network_admin() ) {
146 $options = (array) get_site_option( 'wp_stream_network', array() );
147 $option = isset( $options['general_site_access'] ) ? absint( $options['general_site_access'] ) : 1;
148
149 $this->disable_access = ( $option ) ? false : true;
150 }
151
152 // Register settings page.
153 if ( ! $this->disable_access ) {
154 add_action( 'admin_menu', array( $this, 'register_menu' ) );
155 }
156
157 // Admin notices.
158 add_action( 'admin_notices', array( $this, 'prepare_admin_notices' ) );
159 add_action( 'shutdown', array( $this, 'admin_notices' ) );
160
161 // Add admin body class.
162 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
163
164 // Plugin action links.
165 add_filter(
166 'plugin_action_links',
167 array(
168 $this,
169 'plugin_action_links',
170 ),
171 10,
172 2
173 );
174
175 // Load admin scripts and styles.
176 add_action(
177 'admin_enqueue_scripts',
178 array(
179 $this,
180 'admin_enqueue_scripts',
181 )
182 );
183 add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
184
185 // Reset Streams database.
186 add_action(
187 'wp_ajax_wp_stream_reset',
188 array(
189 $this,
190 'wp_ajax_reset',
191 )
192 );
193
194 // Auto purge setup.
195 add_action( 'wp_loaded', array( $this, 'purge_schedule_setup' ) );
196 add_action(
197 'wp_stream_auto_purge',
198 array(
199 $this,
200 'purge_scheduled_action',
201 )
202 );
203
204 // Ajax users list.
205 add_action(
206 'wp_ajax_wp_stream_filters',
207 array(
208 $this,
209 'ajax_filters',
210 )
211 );
212 }
213
214 /**
215 * Load admin classes
216 *
217 * @action init
218 */
219 public function init() {
220 $this->network = new Network( $this->plugin );
221 $this->live_update = new Live_Update( $this->plugin );
222 $this->export = new Export( $this->plugin );
223 }
224
225 /**
226 * Output specific updates passed as URL parameters.
227 *
228 * @action admin_notices
229 *
230 * @return void
231 */
232 public function prepare_admin_notices() {
233 $message = wp_stream_filter_input( INPUT_GET, 'message' );
234
235 switch ( $message ) {
236 case 'settings_reset':
237 $this->notice( esc_html__( 'All site settings have been successfully reset.', 'stream' ) );
238 break;
239 }
240 }
241
242 /**
243 * Handle notice messages according to the appropriate context (WP-CLI or the WP Admin)
244 *
245 * @param string $message Message to output.
246 * @param bool $is_error If the message is error_level (true) or warning (false).
247 */
248 public function notice( $message, $is_error = true ) {
249 if ( defined( 'WP_CLI' ) && WP_CLI ) {
250 $message = wp_strip_all_tags( $message );
251
252 if ( $is_error ) {
253 WP_CLI::warning( $message );
254 } else {
255 WP_CLI::success( $message );
256 }
257 } else {
258 // Trigger admin notices late, so that any notices which occur during page load are displayed.
259 add_action( 'shutdown', array( $this, 'admin_notices' ) );
260
261 $notice = compact( 'message', 'is_error' );
262
263 if ( ! in_array( $notice, $this->notices, true ) ) {
264 $this->notices[] = $notice;
265 }
266 }
267 }
268
269 /**
270 * Show an error or other message in the WP Admin
271 *
272 * @action shutdown
273 */
274 public function admin_notices() {
275 global $allowedposttags;
276
277 $custom = array(
278 'progress' => array(
279 'class' => true,
280 'id' => true,
281 'max' => true,
282 'style' => true,
283 'value' => true,
284 ),
285 );
286
287 $allowed_html = array_merge( $allowedposttags, $custom );
288
289 ksort( $allowed_html );
290
291 foreach ( $this->notices as $notice ) {
292 $class_name = empty( $notice['is_error'] ) ? 'updated' : 'error';
293 $html_message = sprintf( '<div class="%s">%s</div>', esc_attr( $class_name ), wpautop( $notice['message'] ) );
294
295 echo wp_kses( $html_message, $allowed_html );
296 }
297 }
298
299 /**
300 * Register menu page
301 *
302 * @action admin_menu
303 *
304 * @return void
305 */
306 public function register_menu() {
307 /**
308 * Filter the main admin menu title
309 *
310 * @return string
311 */
312 $main_menu_title = apply_filters( 'wp_stream_admin_menu_title', esc_html__( 'Stream', 'stream' ) );
313
314 /**
315 * Filter the main admin menu position
316 *
317 * Note: Using longtail decimal string to reduce the chance of position conflicts, see Codex
318 *
319 * @return string
320 */
321 $main_menu_position = apply_filters( 'wp_stream_menu_position', '2.999999' );
322
323 /**
324 * Filter the main admin page title
325 *
326 * @return string
327 */
328 $main_page_title = apply_filters( 'wp_stream_admin_page_title', esc_html__( 'Stream Records', 'stream' ) );
329
330 $this->screen_id['main'] = add_menu_page(
331 $main_page_title,
332 $main_menu_title,
333 $this->view_cap,
334 $this->records_page_slug,
335 array( $this, 'render_list_table' ),
336 'div',
337 $main_menu_position
338 );
339
340 /**
341 * Fires before submenu items are added to the Stream menu
342 * allowing plugins to add menu items before Settings
343 *
344 * @return void
345 */
346 do_action( 'wp_stream_admin_menu' );
347
348 /**
349 * Filter the Settings admin page title
350 *
351 * @return string
352 */
353 $settings_page_title = apply_filters( 'wp_stream_settings_form_title', esc_html__( 'Stream Settings', 'stream' ) );
354
355 $this->screen_id['settings'] = add_submenu_page(
356 $this->records_page_slug,
357 $settings_page_title,
358 esc_html__( 'Settings', 'stream' ),
359 $this->settings_cap,
360 $this->settings_page_slug,
361 array( $this, 'render_settings_page' )
362 );
363
364 if ( isset( $this->screen_id['main'] ) ) {
365 /**
366 * Fires just before the Stream list table is registered.
367 *
368 * @return void
369 */
370 do_action( 'wp_stream_admin_menu_screens' );
371
372 // Register the list table early, so it associates the column headers with 'Screen settings'.
373 add_action(
374 'load-' . $this->screen_id['main'],
375 array(
376 $this,
377 'register_list_table',
378 )
379 );
380 }
381 }
382
383 /**
384 * Enqueue scripts/styles for admin screen
385 *
386 * @action admin_enqueue_scripts
387 *
388 * @param string $hook Current hook.
389 *
390 * @return void
391 */
392 public function admin_enqueue_scripts( $hook ) {
393 wp_register_script( 'wp-stream-select2', $this->plugin->locations['url'] . 'ui/lib/select2/js/select2.full.min.js', array( 'jquery' ), '3.5.2', true );
394 wp_register_style( 'wp-stream-select2', $this->plugin->locations['url'] . 'ui/lib/select2/css/select2.min.css', array(), '3.5.2' );
395 wp_register_script( 'wp-stream-timeago', $this->plugin->locations['url'] . 'ui/lib/timeago/jquery.timeago.js', array(), '1.4.1', true );
396
397 $locale = strtolower( substr( get_locale(), 0, 2 ) );
398 $file_tmpl = 'ui/lib/timeago/locales/jquery.timeago.%s.js';
399
400 if ( file_exists( $this->plugin->locations['dir'] . sprintf( $file_tmpl, $locale ) ) ) {
401 wp_register_script(
402 'wp-stream-timeago-locale',
403 $this->plugin->locations['url'] . sprintf( $file_tmpl, $locale ),
404 array( 'wp-stream-timeago' ),
405 '1',
406 false
407 );
408 } else {
409 wp_register_script(
410 'wp-stream-timeago-locale',
411 $this->plugin->locations['url'] . sprintf( $file_tmpl, 'en' ),
412 array( 'wp-stream-timeago' ),
413 '1',
414 false
415 );
416 }
417
418 $min = wp_stream_min_suffix();
419 wp_enqueue_style( 'wp-stream-admin', $this->plugin->locations['url'] . 'ui/css/admin.' . $min . 'css', array(), $this->plugin->get_version() );
420
421 $script_screens = array( 'plugins.php' );
422
423 if ( in_array( $hook, $this->screen_id, true ) || in_array( $hook, $script_screens, true ) ) {
424 wp_enqueue_script( 'wp-stream-select2' );
425 wp_enqueue_style( 'wp-stream-select2' );
426
427 wp_enqueue_script( 'wp-stream-timeago' );
428 wp_enqueue_script( 'wp-stream-timeago-locale' );
429
430 wp_enqueue_script(
431 'wp-stream-admin',
432 $this->plugin->locations['url'] . 'ui/js/admin.' . $min . 'js',
433 array(
434 'jquery',
435 'wp-stream-select2',
436 ),
437 $this->plugin->get_version(),
438 false
439 );
440 wp_enqueue_script(
441 'wp-stream-admin-exclude',
442 $this->plugin->locations['url'] . 'ui/js/exclude.' . $min . 'js',
443 array(
444 'jquery',
445 'wp-stream-select2',
446 ),
447 $this->plugin->get_version(),
448 false
449 );
450 wp_enqueue_script(
451 'wp-stream-live-updates',
452 $this->plugin->locations['url'] . 'ui/js/live-updates.' . $min . 'js',
453 array(
454 'jquery',
455 'heartbeat',
456 ),
457 $this->plugin->get_version(),
458 false
459 );
460
461 wp_localize_script(
462 'wp-stream-admin',
463 'wp_stream',
464 array(
465 'i18n' => array(
466 'confirm_purge' => esc_html__( 'Are you sure you want to delete all Stream activity records from the database? This cannot be undone.', 'stream' ),
467 'confirm_defaults' => esc_html__( 'Are you sure you want to reset all site settings to default? This cannot be undone.', 'stream' ),
468 ),
469 'locale' => esc_js( $locale ),
470 'gmt_offset' => get_option( 'gmt_offset' ),
471 )
472 );
473
474 $order_types = array( 'asc', 'desc' );
475
476 wp_localize_script(
477 'wp-stream-live-updates',
478 'wp_stream_live_updates',
479 array(
480 'current_screen' => $hook,
481 'current_page' => isset( $_GET['paged'] ) ? absint( wp_unslash( $_GET['paged'] ) ) : '1', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
482 'current_order' => isset( $_GET['order'] ) && in_array( strtolower( $_GET['order'] ), $order_types, true ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
483 ? esc_js( $_GET['order'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
484 : 'desc',
485 'current_query' => wp_stream_json_encode( $_GET ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
486 'current_query_count' => count( $_GET ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
487 )
488 );
489 }
490
491 /**
492 * The maximum number of items that can be updated in bulk without receiving a warning.
493 *
494 * Stream watches for bulk actions performed in the WordPress Admin (such as updating
495 * many posts at once) and warns the user before proceeding if the number of items they
496 * are attempting to update exceeds this threshold value. Since Stream will try to save
497 * a log for each item, it will take longer than usual to complete the operation.
498 *
499 * The default threshold is 100 items.
500 *
501 * @return int
502 */
503 $bulk_actions_threshold = apply_filters( 'wp_stream_bulk_actions_threshold', 100 );
504
505 wp_enqueue_script(
506 'wp-stream-global',
507 $this->plugin->locations['url'] . 'ui/js/global.' . $min . 'js',
508 array( 'jquery' ),
509 $this->plugin->get_version(),
510 false
511 );
512
513 wp_localize_script(
514 'wp-stream-global',
515 'wp_stream_global',
516 array(
517 'bulk_actions' => array(
518 'i18n' => array(
519 /* translators: %s: a number of items (e.g. "1,742") */
520 'confirm_action' => sprintf( esc_html__( 'Are you sure you want to perform bulk actions on over %s items? This process could take a while to complete.', 'stream' ), number_format( absint( $bulk_actions_threshold ) ) ),
521 ),
522 'threshold' => absint( $bulk_actions_threshold ),
523 ),
524 'plugins_screen_url' => self_admin_url( 'plugins.php#stream' ),
525 )
526 );
527 }
528
529 /**
530 * Check whether or not the current admin screen belongs to Stream
531 *
532 * @return bool
533 */
534 public function is_stream_screen() {
535 if ( is_admin() && false !== strpos( wp_stream_filter_input( INPUT_GET, 'page' ), $this->records_page_slug ) ) {
536 return true;
537 }
538
539 $screen = get_current_screen();
540 if ( is_admin() && Alerts::POST_TYPE === $screen->post_type ) {
541 return true;
542 }
543
544 return false;
545 }
546
547 /**
548 * Add a specific body class to all Stream admin screens
549 *
550 * @param string $classes CSS classes to output to body.
551 *
552 * @filter admin_body_class
553 *
554 * @return string
555 */
556 public function admin_body_class( $classes ) {
557 $stream_classes = array();
558
559 if ( $this->is_stream_screen() ) {
560 $stream_classes[] = $this->admin_body_class;
561
562 if ( isset( $_GET['page'] ) ) { // // phpcs:ignore WordPress.Security.NonceVerification.Recommended
563 $stream_classes[] = sanitize_key( $_GET['page'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
564 }
565 }
566
567 /**
568 * Filter the Stream admin body classes
569 *
570 * @return array
571 */
572 $stream_classes = apply_filters( 'wp_stream_admin_body_classes', $stream_classes );
573 $stream_classes = implode( ' ', array_map( 'trim', $stream_classes ) );
574
575 return sprintf( '%s %s ', $classes, $stream_classes );
576 }
577
578 /**
579 * Add menu styles for various WP Admin skins
580 *
581 * @uses \wp_add_inline_style()
582 *
583 * @action admin_enqueue_scripts
584 */
585 public function admin_menu_css() {
586 $min = wp_stream_min_suffix();
587 wp_register_style( 'wp-stream-datepicker', $this->plugin->locations['url'] . 'ui/css/datepicker.' . $min . 'css', array(), $this->plugin->get_version() );
588 wp_register_style( 'wp-stream-icons', $this->plugin->locations['url'] . 'ui/stream-icons/style.css', array(), $this->plugin->get_version() );
589
590 // Make sure we're working off a clean version.
591 if ( ! file_exists( ABSPATH . WPINC . '/version.php' ) ) {
592 return;
593 }
594 include ABSPATH . WPINC . '/version.php';
595
596 if ( ! isset( $wp_version ) ) {
597 return;
598 }
599
600 $body_class = $this->admin_body_class;
601 $records_page = $this->records_page_slug;
602 $stream_url = $this->plugin->locations['url'];
603
604 if ( version_compare( $wp_version, '3.8-alpha', '>=' ) ) {
605 wp_enqueue_style( 'wp-stream-icons' );
606
607 $css = "
608 #toplevel_page_{$records_page} .wp-menu-image:before {
609 font-family: 'WP Stream' !important;
610 content: '\\73' !important;
611 }
612 #toplevel_page_{$records_page} .wp-menu-image {
613 background-repeat: no-repeat;
614 }
615 #menu-posts-feedback .wp-menu-image:before {
616 font-family: dashicons !important;
617 content: '\\f175';
618 }
619 #adminmenu #menu-posts-feedback div.wp-menu-image {
620 background: none !important;
621 background-repeat: no-repeat;
622 }
623 body.{$body_class} #wpbody-content .wrap h1:nth-child(1):before {
624 font-family: 'WP Stream' !important;
625 content: '\\73';
626 padding: 0 8px 0 0;
627 }
628 ";
629 } else {
630 $css = "
631 #toplevel_page_{$records_page} .wp-menu-image {
632 background: url( {$stream_url}ui/stream-icons/menuicon-sprite.png ) 0 90% no-repeat;
633 }
634 /* Retina Stream Menu Icon */
635 @media only screen and (-moz-min-device-pixel-ratio: 1.5),
636 only screen and (-o-min-device-pixel-ratio: 3/2),
637 only screen and (-webkit-min-device-pixel-ratio: 1.5),
638 only screen and (min-device-pixel-ratio: 1.5) {
639 #toplevel_page_{$records_page} .wp-menu-image {
640 background: url( {$stream_url}ui/stream-icons/menuicon-sprite-2x.png ) 0 90% no-repeat;
641 background-size:30px 64px;
642 }
643 }
644 #toplevel_page_{$records_page}.current .wp-menu-image,
645 #toplevel_page_{$records_page}.wp-has-current-submenu .wp-menu-image,
646 #toplevel_page_{$records_page}:hover .wp-menu-image {
647 background-position: top left;
648 }
649 ";
650 }
651
652 \wp_add_inline_style( 'wp-admin', $css );
653 }
654
655 /**
656 * Handle the reset AJAX request to reset logs.
657 *
658 * @return bool
659 */
660 public function wp_ajax_reset() {
661 check_ajax_referer( 'stream_nonce_reset', 'wp_stream_nonce_reset' );
662
663 if ( ! current_user_can( $this->settings_cap ) ) {
664 wp_die(
665 esc_html__( "You don't have sufficient privileges to do this action.", 'stream' )
666 );
667 }
668
669 $this->erase_stream_records();
670
671 if ( defined( 'WP_STREAM_TESTS' ) && WP_STREAM_TESTS ) {
672 return true;
673 }
674
675 wp_safe_redirect(
676 add_query_arg(
677 array(
678 'page' => is_network_admin() ? $this->network->network_settings_page_slug : $this->settings_page_slug,
679 'message' => 'data_erased',
680 ),
681 self_admin_url( $this->admin_parent_page )
682 )
683 );
684
685 exit;
686 }
687
688 /**
689 * Clears stream records from the database.
690 *
691 * @return void
692 */
693 private function erase_stream_records() {
694 global $wpdb;
695
696 $where = '';
697
698 if ( is_multisite() && ! $this->plugin->is_network_activated() ) {
699 $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
700 }
701
702 $wpdb->query(
703 "DELETE `stream`, `meta`
704 FROM {$wpdb->stream} AS `stream`
705 LEFT JOIN {$wpdb->streammeta} AS `meta`
706 ON `meta`.`record_id` = `stream`.`ID`
707 WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
708 );
709 }
710
711 /**
712 * Schedules a purge of records.
713 *
714 * @return void
715 */
716 public function purge_schedule_setup() {
717 if ( ! wp_next_scheduled( 'wp_stream_auto_purge' ) ) {
718 wp_schedule_event( time(), 'twicedaily', 'wp_stream_auto_purge' );
719 }
720 }
721
722 /**
723 * Executes a scheduled purge
724 *
725 * @return void
726 */
727 public function purge_scheduled_action() {
728 global $wpdb;
729
730 // Don't purge when in Network Admin unless Stream is network activated.
731 if (
732 is_multisite()
733 &&
734 is_network_admin()
735 &&
736 ! $this->plugin->is_network_activated()
737 ) {
738 return;
739 }
740
741 $defaults = $this->plugin->settings->get_defaults();
742 if ( is_multisite() && $this->plugin->is_network_activated() ) {
743 $options = (array) get_site_option( 'wp_stream_network', $defaults );
744 } else {
745 $options = (array) get_option( 'wp_stream', $defaults );
746 }
747
748 if ( ! empty( $options['general_keep_records_indefinitely'] ) || ! isset( $options['general_records_ttl'] ) ) {
749 return;
750 }
751
752 $days = $options['general_records_ttl'];
753 $timezone = new DateTimeZone( 'UTC' );
754 $date = new DateTime( 'now', $timezone );
755
756 $date->sub( DateInterval::createFromDateString( "$days days" ) );
757
758 $where = $wpdb->prepare( ' AND `stream`.`created` < %s', $date->format( 'Y-m-d H:i:s' ) );
759
760 // Multisite but NOT network activated, only purge the current blog.
761 if ( is_multisite() && ! $this->plugin->is_network_activated() ) {
762 $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
763 }
764
765 $wpdb->query(
766 "DELETE `stream`, `meta`
767 FROM {$wpdb->stream} AS `stream`
768 LEFT JOIN {$wpdb->streammeta} AS `meta`
769 ON `meta`.`record_id` = `stream`.`ID`
770 WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
771 );
772 }
773
774 /**
775 * Returns the admin action links.
776 *
777 * @filter plugin_action_links
778 *
779 * @param array $links Action links.
780 * @param string $file Plugin file.
781 *
782 * @return array
783 */
784 public function plugin_action_links( $links, $file ) {
785 if ( plugin_basename( $this->plugin->locations['dir'] . 'stream.php' ) !== $file ) {
786 return $links;
787 }
788
789 // Also don't show links in Network Admin if Stream isn't network enabled.
790 if ( is_network_admin() && is_multisite() && ! $this->plugin->is_network_activated() ) {
791 return $links;
792 }
793
794 if ( is_network_admin() ) {
795 $admin_page_url = add_query_arg(
796 array(
797 'page' => $this->network->network_settings_page_slug,
798 ),
799 network_admin_url( $this->admin_parent_page )
800 );
801 } else {
802 $admin_page_url = add_query_arg(
803 array(
804 'page' => $this->settings_page_slug,
805 ),
806 admin_url( $this->admin_parent_page )
807 );
808 }
809
810 $links[] = sprintf( '<a href="%s">%s</a>', esc_url( $admin_page_url ), esc_html__( 'Settings', 'default' ) );
811
812 return $links;
813 }
814
815 /**
816 * Render main page
817 */
818 public function render_list_table() {
819 $this->list_table->prepare_items();
820 ?>
821 <div class="wrap">
822 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
823 <?php $this->list_table->display(); ?>
824 </div>
825 <?php
826 }
827
828 /**
829 * Render settings page
830 */
831 public function render_settings_page() {
832 $option_key = $this->plugin->settings->option_key;
833 $form_action = apply_filters( 'wp_stream_settings_form_action', admin_url( 'options.php' ) );
834
835 $page_description = apply_filters( 'wp_stream_settings_form_description', '' );
836
837 $sections = $this->plugin->settings->get_fields();
838 $active_tab = wp_stream_filter_input( INPUT_GET, 'tab' );
839 $min = wp_stream_min_suffix();
840 wp_enqueue_script( 'wp-stream-settings', $this->plugin->locations['url'] . 'ui/js/settings.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version(), true );
841 ?>
842 <div class="wrap">
843 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
844
845 <?php if ( ! empty( $page_description ) ) : ?>
846 <p><?php echo esc_html( $page_description ); ?></p>
847 <?php endif; ?>
848
849 <?php settings_errors(); ?>
850
851 <?php if ( count( $sections ) > 1 ) : ?>
852 <h2 class="nav-tab-wrapper">
853 <?php $i = 0; ?>
854 <?php foreach ( $sections as $section => $data ) : ?>
855 <?php $i++; ?>
856 <?php $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section ); ?>
857 <a href="<?php echo esc_url( add_query_arg( 'tab', $section ) ); ?>" class="nav-tab <?php echo $is_active ? esc_attr( ' nav-tab-active' ) : ''; ?>">
858 <?php echo esc_html( $data['title'] ); ?>
859 </a>
860 <?php endforeach; ?>
861 </h2>
862 <?php endif; ?>
863
864 <div class="nav-tab-content" id="tab-content-settings">
865 <form method="post" action="<?php echo esc_attr( $form_action ); ?>" enctype="multipart/form-data">
866 <div class="settings-sections">
867 <?php
868 $i = 0;
869 foreach ( $sections as $section => $data ) {
870 $i++;
871
872 $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section );
873
874 if ( $is_active ) {
875 settings_fields( $option_key );
876 do_settings_sections( $option_key );
877 }
878 }
879 ?>
880 </div>
881 <?php submit_button(); ?>
882 </form>
883 </div>
884 </div>
885 <?php
886 }
887
888 /**
889 * Instantiate the list table
890 */
891 public function register_list_table() {
892 $this->list_table = new List_Table(
893 $this->plugin,
894 array(
895 'screen' => $this->screen_id['main'],
896 )
897 );
898 }
899
900 /**
901 * Check if a particular role has access
902 *
903 * @param string $role User role.
904 *
905 * @return bool
906 */
907 private function role_can_view( $role ) {
908 if ( in_array( $role, $this->plugin->settings->options['general_role_access'], true ) ) {
909 return true;
910 }
911
912 return false;
913 }
914
915 /**
916 * Filter user caps to dynamically grant our view cap based on allowed roles
917 *
918 * @param array $allcaps All capabilities.
919 * @param array $caps Required caps.
920 * @param array $args Unused.
921 * @param WP_User $user User.
922 *
923 * @filter user_has_cap
924 *
925 * @return array
926 */
927 public function filter_user_caps( $allcaps, $caps, $args, $user = null ) {
928 global $wp_roles;
929
930 $_wp_roles = isset( $wp_roles ) ? $wp_roles : new WP_Roles();
931
932 $user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
933
934 // @see
935 // https://github.com/WordPress/WordPress/blob/c67c9565f1495255807069fdb39dac914046b1a0/wp-includes/capabilities.php#L758
936 $roles = array_unique(
937 array_merge(
938 $user->roles,
939 array_filter(
940 array_keys( $user->caps ),
941 array( $_wp_roles, 'is_role' )
942 )
943 )
944 );
945
946 $stream_view_caps = array( $this->view_cap );
947
948 foreach ( $caps as $cap ) {
949 if ( in_array( $cap, $stream_view_caps, true ) ) {
950 foreach ( $roles as $role ) {
951 if ( $this->role_can_view( $role ) ) {
952 $allcaps[ $cap ] = true;
953
954 break 2;
955 }
956 }
957 }
958 }
959
960 return $allcaps;
961 }
962
963 /**
964 * Filter role caps to dynamically grant our view cap based on allowed roles
965 *
966 * @filter role_has_cap
967 *
968 * @param array $allcaps All capabilities.
969 * @param string $cap Require cap.
970 * @param string $role User role.
971 *
972 * @return array
973 */
974 public function filter_role_caps( $allcaps, $cap, $role ) {
975 $stream_view_caps = array( $this->view_cap );
976
977 if ( in_array( $cap, $stream_view_caps, true ) && $this->role_can_view( $role ) ) {
978 $allcaps[ $cap ] = true;
979 }
980
981 return $allcaps;
982 }
983
984 /**
985 * Ajax callback for return a user list.
986 *
987 * @action wp_ajax_wp_stream_filters
988 */
989 public function ajax_filters() {
990 if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
991 wp_die( '-1' );
992 }
993
994 check_ajax_referer( 'stream_filters_user_search_nonce', 'nonce' );
995
996 switch ( wp_stream_filter_input( INPUT_GET, 'filter' ) ) {
997 case 'user_id':
998 $users = array_merge(
999 array(
1000 0 => (object) array(
1001 'display_name' => 'WP-CLI',
1002 ),
1003 ),
1004 get_users()
1005 );
1006
1007 $search = wp_stream_filter_input( INPUT_GET, 'q' );
1008 if ( $search ) {
1009 // `search` arg for get_users() is not enough
1010 $users = array_filter(
1011 $users,
1012 function ( $user ) use ( $search ) {
1013 return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( $search ) );
1014 }
1015 );
1016 }
1017
1018 if ( count( $users ) > $this->preload_users_max ) {
1019 $users = array_slice( $users, 0, $this->preload_users_max );
1020 }
1021
1022 // Get gravatar / roles for final result set.
1023 $results = $this->get_users_record_meta( $users );
1024
1025 break;
1026 }
1027
1028 if ( isset( $results ) ) {
1029 echo wp_stream_json_encode( $results ); // xss ok.
1030 }
1031
1032 die();
1033 }
1034
1035 /**
1036 * Return relevant user meta data.
1037 *
1038 * @param array $authors Author data.
1039 * @return array
1040 */
1041 public function get_users_record_meta( $authors ) {
1042 $authors_records = array();
1043
1044 foreach ( $authors as $user_id => $args ) {
1045 $author = new Author( $args->ID );
1046
1047 $authors_records[ $user_id ] = array(
1048 'text' => $author->get_display_name(),
1049 'id' => $author->id,
1050 'label' => $author->get_display_name(),
1051 'icon' => $author->get_avatar_src( 32 ),
1052 'title' => '',
1053 );
1054 }
1055
1056 return $authors_records;
1057 }
1058
1059 /**
1060 * Get user meta in a way that is also safe for VIP
1061 *
1062 * @param int $user_id User ID.
1063 * @param string $meta_key Meta key.
1064 * @param bool $single Return first found meta value connected to the meta key (optional).
1065 *
1066 * @return mixed
1067 */
1068 public function get_user_meta( $user_id, $meta_key, $single = true ) {
1069 if ( wp_stream_is_vip() && function_exists( 'get_user_attribute' ) ) {
1070 return get_user_attribute( $user_id, $meta_key );
1071 }
1072
1073 return get_user_meta( $user_id, $meta_key, $single );
1074 }
1075
1076 /**
1077 * Update user meta in a way that is also safe for VIP
1078 *
1079 * @param int $user_id User ID.
1080 * @param string $meta_key Meta key.
1081 * @param mixed $meta_value Meta value.
1082 * @param mixed $prev_value Previous meta value being overwritten (optional).
1083 *
1084 * @return int|bool
1085 */
1086 public function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
1087 if ( wp_stream_is_vip() && function_exists( 'update_user_attribute' ) ) {
1088 return update_user_attribute( $user_id, $meta_key, $meta_value );
1089 }
1090
1091 return update_user_meta( $user_id, $meta_key, $meta_value, $prev_value );
1092 }
1093
1094 /**
1095 * Delete user meta in a way that is also safe for VIP
1096 *
1097 * @param int $user_id User ID.
1098 * @param string $meta_key Meta key.
1099 * @param mixed $meta_value Meta value (optional).
1100 *
1101 * @return bool
1102 */
1103 public function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
1104 if ( wp_stream_is_vip() && function_exists( 'delete_user_attribute' ) ) {
1105 return delete_user_attribute( $user_id, $meta_key, $meta_value );
1106 }
1107
1108 return delete_user_meta( $user_id, $meta_key, $meta_value );
1109 }
1110 }
1111