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

1,131 lines 29.1 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', $plugin->settings->get_defaults() );
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 $defaults = $this->plugin->settings->get_defaults();
750 if ( is_multisite() && $this->plugin->is_network_activated() ) {
751 $options = (array) get_site_option( 'wp_stream_network', $defaults );
752 } else {
753 $options = (array) get_option( 'wp_stream', $defaults );
754 }
755
756 if ( ! empty( $options['general_keep_records_indefinitely'] ) || ! isset( $options['general_records_ttl'] ) ) {
757 return;
758 }
759
760 $days = $options['general_records_ttl'];
761 $timezone = new DateTimeZone( 'UTC' );
762 $date = new DateTime( 'now', $timezone );
763
764 $date->sub( DateInterval::createFromDateString( "$days days" ) );
765
766 $where = $wpdb->prepare( ' AND `stream`.`created` < %s', $date->format( 'Y-m-d H:i:s' ) );
767
768 // Multisite but NOT network activated, only purge the current blog.
769 if ( is_multisite() && ! $this->plugin->is_network_activated() ) {
770 $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
771 }
772
773 $wpdb->query(
774 "DELETE `stream`, `meta`
775 FROM {$wpdb->stream} AS `stream`
776 LEFT JOIN {$wpdb->streammeta} AS `meta`
777 ON `meta`.`record_id` = `stream`.`ID`
778 WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
779 );
780 }
781
782 /**
783 * Returns the admin action links.
784 *
785 * @filter plugin_action_links
786 *
787 * @param array $links Action links.
788 * @param string $file Plugin file.
789 *
790 * @return array
791 */
792 public function plugin_action_links( $links, $file ) {
793 if ( plugin_basename( $this->plugin->locations['dir'] . 'stream.php' ) !== $file ) {
794 return $links;
795 }
796
797 // Also don't show links in Network Admin if Stream isn't network enabled.
798 if ( is_network_admin() && is_multisite() && ! $this->plugin->is_network_activated() ) {
799 return $links;
800 }
801
802 if ( is_network_admin() ) {
803 $admin_page_url = add_query_arg(
804 array(
805 'page' => $this->network->network_settings_page_slug,
806 ),
807 network_admin_url( $this->admin_parent_page )
808 );
809 } else {
810 $admin_page_url = add_query_arg(
811 array(
812 'page' => $this->settings_page_slug,
813 ),
814 admin_url( $this->admin_parent_page )
815 );
816 }
817
818 $links[] = sprintf( '<a href="%s">%s</a>', esc_url( $admin_page_url ), esc_html__( 'Settings', 'default' ) );
819
820 if ( ! defined( 'DISALLOW_FILE_MODS' ) || false === DISALLOW_FILE_MODS ) {
821 $url = add_query_arg(
822 array(
823 'action' => 'wp_stream_uninstall',
824 'wp_stream_nonce' => wp_create_nonce( 'stream_nonce' ),
825 ),
826 admin_url( 'admin-ajax.php' )
827 );
828
829 $links[] = sprintf( '<span id="wp_stream_uninstall" class="delete"><a href="%s">%s</a></span>', esc_url( $url ), esc_html__( 'Uninstall', 'stream' ) );
830 }
831
832 return $links;
833 }
834
835 /**
836 * Render main page
837 */
838 public function render_list_table() {
839 $this->list_table->prepare_items();
840 ?>
841 <div class="wrap">
842 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
843 <?php $this->list_table->display(); ?>
844 </div>
845 <?php
846 }
847
848 /**
849 * Render settings page
850 */
851 public function render_settings_page() {
852 $option_key = $this->plugin->settings->option_key;
853 $form_action = apply_filters( 'wp_stream_settings_form_action', admin_url( 'options.php' ) );
854
855 $page_description = apply_filters( 'wp_stream_settings_form_description', '' );
856
857 $sections = $this->plugin->settings->get_fields();
858 $active_tab = wp_stream_filter_input( INPUT_GET, 'tab' );
859 $min = wp_stream_min_suffix();
860 wp_enqueue_script( 'wp-stream-settings', $this->plugin->locations['url'] . 'ui/js/settings.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version(), true );
861 ?>
862 <div class="wrap">
863 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
864
865 <?php if ( ! empty( $page_description ) ) : ?>
866 <p><?php echo esc_html( $page_description ); ?></p>
867 <?php endif; ?>
868
869 <?php settings_errors(); ?>
870
871 <?php if ( count( $sections ) > 1 ) : ?>
872 <h2 class="nav-tab-wrapper">
873 <?php $i = 0; ?>
874 <?php foreach ( $sections as $section => $data ) : ?>
875 <?php $i++; ?>
876 <?php $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section ); ?>
877 <a href="<?php echo esc_url( add_query_arg( 'tab', $section ) ); ?>" class="nav-tab <?php echo $is_active ? esc_attr( ' nav-tab-active' ) : ''; ?>">
878 <?php echo esc_html( $data['title'] ); ?>
879 </a>
880 <?php endforeach; ?>
881 </h2>
882 <?php endif; ?>
883
884 <div class="nav-tab-content" id="tab-content-settings">
885 <form method="post" action="<?php echo esc_attr( $form_action ); ?>" enctype="multipart/form-data">
886 <div class="settings-sections">
887 <?php
888 $i = 0;
889 foreach ( $sections as $section => $data ) {
890 $i++;
891
892 $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section );
893
894 if ( $is_active ) {
895 settings_fields( $option_key );
896 do_settings_sections( $option_key );
897 }
898 }
899 ?>
900 </div>
901 <?php submit_button(); ?>
902 </form>
903 </div>
904 </div>
905 <?php
906 }
907
908 /**
909 * Instantiate the list table
910 */
911 public function register_list_table() {
912 $this->list_table = new List_Table(
913 $this->plugin,
914 array(
915 'screen' => $this->screen_id['main'],
916 )
917 );
918 }
919
920 /**
921 * Check if a particular role has access
922 *
923 * @param string $role User role.
924 *
925 * @return bool
926 */
927 private function role_can_view( $role ) {
928 if ( in_array( $role, $this->plugin->settings->options['general_role_access'], true ) ) {
929 return true;
930 }
931
932 return false;
933 }
934
935 /**
936 * Filter user caps to dynamically grant our view cap based on allowed roles
937 *
938 * @param array $allcaps All capabilities.
939 * @param array $caps Required caps.
940 * @param array $args Unused.
941 * @param WP_User $user User.
942 *
943 * @filter user_has_cap
944 *
945 * @return array
946 */
947 public function filter_user_caps( $allcaps, $caps, $args, $user = null ) {
948 global $wp_roles;
949
950 $_wp_roles = isset( $wp_roles ) ? $wp_roles : new WP_Roles();
951
952 $user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
953
954 // @see
955 // https://github.com/WordPress/WordPress/blob/c67c9565f1495255807069fdb39dac914046b1a0/wp-includes/capabilities.php#L758
956 $roles = array_unique(
957 array_merge(
958 $user->roles,
959 array_filter(
960 array_keys( $user->caps ),
961 array( $_wp_roles, 'is_role' )
962 )
963 )
964 );
965
966 $stream_view_caps = array( $this->view_cap );
967
968 foreach ( $caps as $cap ) {
969 if ( in_array( $cap, $stream_view_caps, true ) ) {
970 foreach ( $roles as $role ) {
971 if ( $this->role_can_view( $role ) ) {
972 $allcaps[ $cap ] = true;
973
974 break 2;
975 }
976 }
977 }
978 }
979
980 return $allcaps;
981 }
982
983 /**
984 * Filter role caps to dynamically grant our view cap based on allowed roles
985 *
986 * @filter role_has_cap
987 *
988 * @param array $allcaps All capabilities.
989 * @param string $cap Require cap.
990 * @param string $role User role.
991 *
992 * @return array
993 */
994 public function filter_role_caps( $allcaps, $cap, $role ) {
995 $stream_view_caps = array( $this->view_cap );
996
997 if ( in_array( $cap, $stream_view_caps, true ) && $this->role_can_view( $role ) ) {
998 $allcaps[ $cap ] = true;
999 }
1000
1001 return $allcaps;
1002 }
1003
1004 /**
1005 * Ajax callback for return a user list.
1006 *
1007 * @action wp_ajax_wp_stream_filters
1008 */
1009 public function ajax_filters() {
1010 if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
1011 wp_die( '-1' );
1012 }
1013
1014 check_ajax_referer( 'stream_filters_user_search_nonce', 'nonce' );
1015
1016 switch ( wp_stream_filter_input( INPUT_GET, 'filter' ) ) {
1017 case 'user_id':
1018 $users = array_merge(
1019 array(
1020 0 => (object) array(
1021 'display_name' => 'WP-CLI',
1022 ),
1023 ),
1024 get_users()
1025 );
1026
1027 $search = wp_stream_filter_input( INPUT_GET, 'q' );
1028 if ( $search ) {
1029 // `search` arg for get_users() is not enough
1030 $users = array_filter(
1031 $users,
1032 function ( $user ) use ( $search ) {
1033 return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( $search ) );
1034 }
1035 );
1036 }
1037
1038 if ( count( $users ) > $this->preload_users_max ) {
1039 $users = array_slice( $users, 0, $this->preload_users_max );
1040 }
1041
1042 // Get gravatar / roles for final result set.
1043 $results = $this->get_users_record_meta( $users );
1044
1045 break;
1046 }
1047
1048 if ( isset( $results ) ) {
1049 echo wp_stream_json_encode( $results ); // xss ok.
1050 }
1051
1052 die();
1053 }
1054
1055 /**
1056 * Return relevant user meta data.
1057 *
1058 * @param array $authors Author data.
1059 * @return array
1060 */
1061 public function get_users_record_meta( $authors ) {
1062 $authors_records = array();
1063
1064 foreach ( $authors as $user_id => $args ) {
1065 $author = new Author( $args->ID );
1066
1067 $authors_records[ $user_id ] = array(
1068 'text' => $author->get_display_name(),
1069 'id' => $author->id,
1070 'label' => $author->get_display_name(),
1071 'icon' => $author->get_avatar_src( 32 ),
1072 'title' => '',
1073 );
1074 }
1075
1076 return $authors_records;
1077 }
1078
1079 /**
1080 * Get user meta in a way that is also safe for VIP
1081 *
1082 * @param int $user_id User ID.
1083 * @param string $meta_key Meta key.
1084 * @param bool $single Return first found meta value connected to the meta key (optional).
1085 *
1086 * @return mixed
1087 */
1088 public function get_user_meta( $user_id, $meta_key, $single = true ) {
1089 if ( wp_stream_is_vip() && function_exists( 'get_user_attribute' ) ) {
1090 return get_user_attribute( $user_id, $meta_key );
1091 }
1092
1093 return get_user_meta( $user_id, $meta_key, $single );
1094 }
1095
1096 /**
1097 * Update user meta in a way that is also safe for VIP
1098 *
1099 * @param int $user_id User ID.
1100 * @param string $meta_key Meta key.
1101 * @param mixed $meta_value Meta value.
1102 * @param mixed $prev_value Previous meta value being overwritten (optional).
1103 *
1104 * @return int|bool
1105 */
1106 public function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
1107 if ( wp_stream_is_vip() && function_exists( 'update_user_attribute' ) ) {
1108 return update_user_attribute( $user_id, $meta_key, $meta_value );
1109 }
1110
1111 return update_user_meta( $user_id, $meta_key, $meta_value, $prev_value );
1112 }
1113
1114 /**
1115 * Delete user meta in a way that is also safe for VIP
1116 *
1117 * @param int $user_id User ID.
1118 * @param string $meta_key Meta key.
1119 * @param mixed $meta_value Meta value (optional).
1120 *
1121 * @return bool
1122 */
1123 public function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
1124 if ( wp_stream_is_vip() && function_exists( 'delete_user_attribute' ) ) {
1125 return delete_user_attribute( $user_id, $meta_key, $meta_value );
1126 }
1127
1128 return delete_user_meta( $user_id, $meta_key, $meta_value );
1129 }
1130 }
1131