PluginProbe
Stream – Activity Log & Audit Trail / 3.6.0
Stream – Activity Log & Audit Trail v3.6.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.6.0, at classes/class-admin.php

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