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

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