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

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