PluginProbe
Stream – Activity Log & Audit Trail / 3.10.0
Stream – Activity Log & Audit Trail v3.10.0
4.4.0 4.3.0 4.2.2 4.2.1 trunk 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.1 3.1.1 3.10.0 3.2.0 3.2.1 3.2.2 3.2.3 All 50 releases
stream / classes / class-admin.php

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

1,104 lines 27.9 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() ) {
536 return false;
537 }
538
539 $page = wp_stream_filter_input( INPUT_GET, 'page' );
540 if ( is_string( $page ) && false !== strpos( $page, $this->records_page_slug ) ) {
541 return true;
542 }
543
544 $screen = get_current_screen();
545 if ( Alerts::POST_TYPE === $screen->post_type ) {
546 return true;
547 }
548
549 return false;
550 }
551
552 /**
553 * Add a specific body class to all Stream admin screens
554 *
555 * @param string $classes CSS classes to output to body.
556 *
557 * @filter admin_body_class
558 *
559 * @return string
560 */
561 public function admin_body_class( $classes ) {
562 $stream_classes = array();
563
564 if ( $this->is_stream_screen() ) {
565 $stream_classes[] = $this->admin_body_class;
566
567 if ( isset( $_GET['page'] ) ) { // // phpcs:ignore WordPress.Security.NonceVerification.Recommended
568 $stream_classes[] = sanitize_key( $_GET['page'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
569 }
570 }
571
572 /**
573 * Filter the Stream admin body classes
574 *
575 * @return array
576 */
577 $stream_classes = apply_filters( 'wp_stream_admin_body_classes', $stream_classes );
578 $stream_classes = implode( ' ', array_map( 'trim', $stream_classes ) );
579
580 return sprintf( '%s %s ', $classes, $stream_classes );
581 }
582
583 /**
584 * Add menu styles for various WP Admin skins
585 *
586 * @uses \wp_add_inline_style()
587 *
588 * @action admin_enqueue_scripts
589 */
590 public function admin_menu_css() {
591 $min = wp_stream_min_suffix();
592 wp_register_style( 'wp-stream-datepicker', $this->plugin->locations['url'] . 'ui/css/datepicker.' . $min . 'css', array(), $this->plugin->get_version() );
593 wp_register_style( 'wp-stream-icons', $this->plugin->locations['url'] . 'ui/stream-icons/style.css', array(), $this->plugin->get_version() );
594
595 // Make sure we're working off a clean version.
596 if ( ! file_exists( ABSPATH . WPINC . '/version.php' ) ) {
597 return;
598 }
599 include ABSPATH . WPINC . '/version.php';
600
601 if ( ! isset( $wp_version ) ) {
602 return;
603 }
604
605 $body_class = $this->admin_body_class;
606 $records_page = $this->records_page_slug;
607 $stream_url = $this->plugin->locations['url'];
608
609 if ( version_compare( $wp_version, '3.8-alpha', '>=' ) ) {
610 wp_enqueue_style( 'wp-stream-icons' );
611
612 $css = "
613 #toplevel_page_{$records_page} .wp-menu-image:before {
614 font-family: 'WP Stream' !important;
615 content: '\\73' !important;
616 }
617 #toplevel_page_{$records_page} .wp-menu-image {
618 background-repeat: no-repeat;
619 }
620 #menu-posts-feedback .wp-menu-image:before {
621 font-family: dashicons !important;
622 content: '\\f175';
623 }
624 #adminmenu #menu-posts-feedback div.wp-menu-image {
625 background: none !important;
626 background-repeat: no-repeat;
627 }
628 body.{$body_class} #wpbody-content .wrap h1:nth-child(1):before {
629 font-family: 'WP Stream' !important;
630 content: '\\73';
631 padding: 0 8px 0 0;
632 }
633 ";
634 } else {
635 $css = "
636 #toplevel_page_{$records_page} .wp-menu-image {
637 background: url( {$stream_url}ui/stream-icons/menuicon-sprite.png ) 0 90% no-repeat;
638 }
639 /* Retina Stream Menu Icon */
640 @media only screen and (-moz-min-device-pixel-ratio: 1.5),
641 only screen and (-o-min-device-pixel-ratio: 3/2),
642 only screen and (-webkit-min-device-pixel-ratio: 1.5),
643 only screen and (min-device-pixel-ratio: 1.5) {
644 #toplevel_page_{$records_page} .wp-menu-image {
645 background: url( {$stream_url}ui/stream-icons/menuicon-sprite-2x.png ) 0 90% no-repeat;
646 background-size:30px 64px;
647 }
648 }
649 #toplevel_page_{$records_page}.current .wp-menu-image,
650 #toplevel_page_{$records_page}.wp-has-current-submenu .wp-menu-image,
651 #toplevel_page_{$records_page}:hover .wp-menu-image {
652 background-position: top left;
653 }
654 ";
655 }
656
657 \wp_add_inline_style( 'wp-admin', $css );
658 }
659
660 /**
661 * Handle the reset AJAX request to reset logs.
662 *
663 * @return bool
664 */
665 public function wp_ajax_reset() {
666 check_ajax_referer( 'stream_nonce_reset', 'wp_stream_nonce_reset' );
667
668 if ( ! current_user_can( $this->settings_cap ) ) {
669 wp_die(
670 esc_html__( "You don't have sufficient privileges to do this action.", 'stream' )
671 );
672 }
673
674 $this->erase_stream_records();
675
676 if ( defined( 'WP_STREAM_TESTS' ) && WP_STREAM_TESTS ) {
677 return true;
678 }
679
680 wp_safe_redirect(
681 add_query_arg(
682 array(
683 'page' => is_network_admin() ? $this->network->network_settings_page_slug : $this->settings_page_slug,
684 'message' => 'data_erased',
685 ),
686 self_admin_url( $this->admin_parent_page )
687 )
688 );
689
690 exit;
691 }
692
693 /**
694 * Clears stream records from the database.
695 *
696 * @return void
697 */
698 private function erase_stream_records() {
699 global $wpdb;
700
701 $where = '';
702
703 if ( is_multisite() && ! $this->plugin->is_network_activated() ) {
704 $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
705 }
706
707 $wpdb->query(
708 "DELETE `stream`, `meta`
709 FROM {$wpdb->stream} AS `stream`
710 LEFT JOIN {$wpdb->streammeta} AS `meta`
711 ON `meta`.`record_id` = `stream`.`ID`
712 WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
713 );
714 }
715
716 /**
717 * Schedules a purge of records.
718 *
719 * @return void
720 */
721 public function purge_schedule_setup() {
722 if ( ! wp_next_scheduled( 'wp_stream_auto_purge' ) ) {
723 wp_schedule_event( time(), 'twicedaily', 'wp_stream_auto_purge' );
724 }
725 }
726
727 /**
728 * Executes a scheduled purge
729 *
730 * @return void
731 */
732 public function purge_scheduled_action() {
733 global $wpdb;
734
735 // Don't purge when in Network Admin unless Stream is network activated.
736 if (
737 is_multisite()
738 &&
739 is_network_admin()
740 &&
741 ! $this->plugin->is_network_activated()
742 ) {
743 return;
744 }
745
746 $defaults = $this->plugin->settings->get_defaults();
747 if ( is_multisite() && $this->plugin->is_network_activated() ) {
748 $options = (array) get_site_option( 'wp_stream_network', $defaults );
749 } else {
750 $options = (array) get_option( 'wp_stream', $defaults );
751 }
752
753 if ( ! empty( $options['general_keep_records_indefinitely'] ) || ! isset( $options['general_records_ttl'] ) ) {
754 return;
755 }
756
757 $days = $options['general_records_ttl'];
758 $timezone = new DateTimeZone( 'UTC' );
759 $date = new DateTime( 'now', $timezone );
760
761 $date->sub( DateInterval::createFromDateString( "$days days" ) );
762
763 $where = $wpdb->prepare( ' AND `stream`.`created` < %s', $date->format( 'Y-m-d H:i:s' ) );
764
765 // Multisite but NOT network activated, only purge the current blog.
766 if ( is_multisite() && ! $this->plugin->is_network_activated() ) {
767 $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
768 }
769
770 $wpdb->query(
771 "DELETE `stream`, `meta`
772 FROM {$wpdb->stream} AS `stream`
773 LEFT JOIN {$wpdb->streammeta} AS `meta`
774 ON `meta`.`record_id` = `stream`.`ID`
775 WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
776 );
777 }
778
779 /**
780 * Returns the admin action links.
781 *
782 * @filter plugin_action_links
783 *
784 * @param array $links Action links.
785 * @param string $file Plugin file.
786 *
787 * @return array
788 */
789 public function plugin_action_links( $links, $file ) {
790 if ( plugin_basename( $this->plugin->locations['dir'] . 'stream.php' ) !== $file ) {
791 return $links;
792 }
793
794 // Also don't show links in Network Admin if Stream isn't network enabled.
795 if ( is_network_admin() && is_multisite() && ! $this->plugin->is_network_activated() ) {
796 return $links;
797 }
798
799 if ( is_network_admin() ) {
800 $admin_page_url = add_query_arg(
801 array(
802 'page' => $this->network->network_settings_page_slug,
803 ),
804 network_admin_url( $this->admin_parent_page )
805 );
806 } else {
807 $admin_page_url = add_query_arg(
808 array(
809 'page' => $this->settings_page_slug,
810 ),
811 admin_url( $this->admin_parent_page )
812 );
813 }
814
815 $links[] = sprintf( '<a href="%s">%s</a>', esc_url( $admin_page_url ), esc_html__( 'Settings', 'default' ) );
816
817 return $links;
818 }
819
820 /**
821 * Render main page
822 */
823 public function render_list_table() {
824 $this->list_table->prepare_items();
825 ?>
826 <div class="wrap">
827 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
828 <?php $this->list_table->display(); ?>
829 </div>
830 <?php
831 }
832
833 /**
834 * Render settings page
835 */
836 public function render_settings_page() {
837 $option_key = $this->plugin->settings->option_key;
838 $form_action = apply_filters( 'wp_stream_settings_form_action', admin_url( 'options.php' ) );
839
840 $page_description = apply_filters( 'wp_stream_settings_form_description', '' );
841
842 $sections = $this->plugin->settings->get_fields();
843 $active_tab = wp_stream_filter_input( INPUT_GET, 'tab' );
844 $min = wp_stream_min_suffix();
845 wp_enqueue_script( 'wp-stream-settings', $this->plugin->locations['url'] . 'ui/js/settings.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version(), true );
846 ?>
847 <div class="wrap">
848 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
849
850 <?php if ( ! empty( $page_description ) ) : ?>
851 <p><?php echo esc_html( $page_description ); ?></p>
852 <?php endif; ?>
853
854 <?php settings_errors(); ?>
855
856 <?php if ( count( $sections ) > 1 ) : ?>
857 <h2 class="nav-tab-wrapper">
858 <?php $i = 0; ?>
859 <?php foreach ( $sections as $section => $data ) : ?>
860 <?php $i++; ?>
861 <?php $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section ); ?>
862 <a href="<?php echo esc_url( add_query_arg( 'tab', $section ) ); ?>" class="nav-tab <?php echo $is_active ? esc_attr( ' nav-tab-active' ) : ''; ?>">
863 <?php echo esc_html( $data['title'] ); ?>
864 </a>
865 <?php endforeach; ?>
866 </h2>
867 <?php endif; ?>
868
869 <div class="nav-tab-content" id="tab-content-settings">
870 <form method="post" action="<?php echo esc_attr( $form_action ); ?>" enctype="multipart/form-data">
871 <div class="settings-sections">
872 <?php
873 $i = 0;
874 foreach ( $sections as $section => $data ) {
875 $i++;
876
877 $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section );
878
879 if ( $is_active ) {
880 settings_fields( $option_key );
881 do_settings_sections( $option_key );
882 }
883 }
884 ?>
885 </div>
886 <?php submit_button(); ?>
887 </form>
888 </div>
889 </div>
890 <?php
891 }
892
893 /**
894 * Instantiate the list table
895 */
896 public function register_list_table() {
897 $this->list_table = new List_Table(
898 $this->plugin,
899 array(
900 'screen' => $this->screen_id['main'],
901 )
902 );
903 }
904
905 /**
906 * Check if a particular role has access
907 *
908 * @param string $role User role.
909 *
910 * @return bool
911 */
912 private function role_can_view( $role ) {
913 if ( in_array( $role, $this->plugin->settings->options['general_role_access'], true ) ) {
914 return true;
915 }
916
917 return false;
918 }
919
920 /**
921 * Filter user caps to dynamically grant our view cap based on allowed roles
922 *
923 * @param array $allcaps All capabilities.
924 * @param array $caps Required caps.
925 * @param array $args Unused.
926 * @param WP_User $user User.
927 *
928 * @filter user_has_cap
929 *
930 * @return array
931 */
932 public function filter_user_caps( $allcaps, $caps, $args, $user = null ) {
933 global $wp_roles;
934
935 $_wp_roles = isset( $wp_roles ) ? $wp_roles : new WP_Roles();
936
937 $user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
938
939 // @see
940 // https://github.com/WordPress/WordPress/blob/c67c9565f1495255807069fdb39dac914046b1a0/wp-includes/capabilities.php#L758
941 $roles = array_unique(
942 array_merge(
943 $user->roles,
944 array_filter(
945 array_keys( $user->caps ),
946 array( $_wp_roles, 'is_role' )
947 )
948 )
949 );
950
951 $stream_view_caps = array( $this->view_cap );
952
953 foreach ( $caps as $cap ) {
954 if ( in_array( $cap, $stream_view_caps, true ) ) {
955 foreach ( $roles as $role ) {
956 if ( $this->role_can_view( $role ) ) {
957 $allcaps[ $cap ] = true;
958
959 break 2;
960 }
961 }
962 }
963 }
964
965 return $allcaps;
966 }
967
968 /**
969 * Filter role caps to dynamically grant our view cap based on allowed roles
970 *
971 * @filter role_has_cap
972 *
973 * @param array $allcaps All capabilities.
974 * @param string $cap Require cap.
975 * @param string $role User role.
976 *
977 * @return array
978 */
979 public function filter_role_caps( $allcaps, $cap, $role ) {
980 $stream_view_caps = array( $this->view_cap );
981
982 if ( in_array( $cap, $stream_view_caps, true ) && $this->role_can_view( $role ) ) {
983 $allcaps[ $cap ] = true;
984 }
985
986 return $allcaps;
987 }
988
989 /**
990 * Ajax callback for return a user list.
991 *
992 * @action wp_ajax_wp_stream_filters
993 */
994 public function ajax_filters() {
995 if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
996 wp_die( '-1' );
997 }
998
999 check_ajax_referer( 'stream_filters_user_search_nonce', 'nonce' );
1000
1001 switch ( wp_stream_filter_input( INPUT_GET, 'filter' ) ) {
1002 case 'user_id':
1003 $users = array_merge(
1004 array(
1005 0 => (object) array(
1006 'display_name' => 'WP-CLI',
1007 ),
1008 ),
1009 get_users()
1010 );
1011
1012 $search = wp_stream_filter_input( INPUT_GET, 'q' );
1013 if ( $search ) {
1014 // `search` arg for get_users() is not enough
1015 $users = array_filter(
1016 $users,
1017 function ( $user ) use ( $search ) {
1018 return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( $search ) );
1019 }
1020 );
1021 }
1022
1023 if ( count( $users ) > $this->preload_users_max ) {
1024 $users = array_slice( $users, 0, $this->preload_users_max );
1025 }
1026
1027 // Get gravatar / roles for final result set.
1028 $results = $this->get_users_record_meta( $users );
1029
1030 break;
1031 }
1032
1033 if ( isset( $results ) ) {
1034 echo wp_stream_json_encode( $results ); // xss ok.
1035 }
1036
1037 die();
1038 }
1039
1040 /**
1041 * Return relevant user meta data.
1042 *
1043 * @param array $authors Author data.
1044 * @return array
1045 */
1046 public function get_users_record_meta( $authors ) {
1047 $authors_records = array();
1048
1049 foreach ( $authors as $user_id => $args ) {
1050 $author = new Author( $args->ID );
1051
1052 $authors_records[ $user_id ] = array(
1053 'text' => $author->get_display_name(),
1054 'id' => $author->id,
1055 'label' => $author->get_display_name(),
1056 'icon' => $author->get_avatar_src( 32 ),
1057 'title' => '',
1058 );
1059 }
1060
1061 return $authors_records;
1062 }
1063
1064 /**
1065 * Get user meta in a way that is also safe for VIP
1066 *
1067 * @param int $user_id User ID.
1068 * @param string $meta_key Meta key.
1069 * @param bool $single Return first found meta value connected to the meta key (optional).
1070 *
1071 * @return mixed
1072 */
1073 public function get_user_meta( $user_id, $meta_key, $single = true ) {
1074 return get_user_meta( $user_id, $meta_key, $single );
1075 }
1076
1077 /**
1078 * Update user meta in a way that is also safe for VIP
1079 *
1080 * @param int $user_id User ID.
1081 * @param string $meta_key Meta key.
1082 * @param mixed $meta_value Meta value.
1083 * @param mixed $prev_value Previous meta value being overwritten (optional).
1084 *
1085 * @return int|bool
1086 */
1087 public function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
1088 return update_user_meta( $user_id, $meta_key, $meta_value, $prev_value );
1089 }
1090
1091 /**
1092 * Delete user meta in a way that is also safe for VIP
1093 *
1094 * @param int $user_id User ID.
1095 * @param string $meta_key Meta key.
1096 * @param mixed $meta_value Meta value (optional).
1097 *
1098 * @return bool
1099 */
1100 public function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
1101 return delete_user_meta( $user_id, $meta_key, $meta_value );
1102 }
1103 }
1104