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

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