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

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

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