PluginProbe
Stream – Activity Log & Audit Trail / trunk
Stream – Activity Log & Audit Trail vtrunk
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-network.php

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

610 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Manages functionality for the Stream Admin pages on both
4 * single and multi-sites.
5 *
6 * @package WP_Stream
7 */
8
9 namespace WP_Stream;
10
11 /**
12 * Class - Network
13 */
14 class Network {
15 /**
16 * Holds instance of plugin object
17 *
18 * @var Plugin
19 */
20 public $plugin;
21
22 /**
23 * Network page slug
24 *
25 * @var string
26 */
27 public $network_settings_page_slug = 'wp_stream_network_settings';
28
29 /**
30 * The option name for the network settings.
31 *
32 * @var string
33 */
34 public $network_settings_option = 'wp_stream_network';
35
36 /**
37 * Class constructor
38 *
39 * @param Plugin $plugin Instance of plugin object.
40 */
41 public function __construct( $plugin ) {
42 $this->plugin = $plugin;
43
44 // Always add default site_id/blog_id params when multisite.
45 if ( is_multisite() ) {
46 add_filter( 'wp_stream_query_args', array( $this, 'network_query_args' ) );
47 }
48
49 // Bail early if not network-activated.
50 if ( ! $this->is_network_activated() ) {
51 return;
52 }
53
54 // Actions.
55 add_action( 'init', array( $this, 'ajax_network_admin' ) );
56 add_action( 'network_admin_menu', array( $this->plugin->admin, 'register_menu' ) );
57 add_action( 'network_admin_menu', array( $this, 'admin_menu_screens' ) );
58 add_action( 'admin_menu', array( $this, 'admin_menu_screens' ) );
59 add_action( 'admin_bar_menu', array( $this, 'network_admin_bar_menu' ), 99 );
60 add_action( 'network_admin_notices', array( $this->plugin->admin, 'admin_notices' ) );
61 add_action( 'wpmuadminedit', array( $this, 'network_options_action' ) );
62 add_action( 'update_site_option_' . $this->plugin->settings->network_options_key, array( $this, 'updated_option_ttl_remove_records' ), 10, 3 );
63
64 // Filters.
65 add_filter( 'wp_stream_blog_id_logged', array( $this, 'blog_id_logged' ) );
66 add_filter( 'wp_stream_admin_page_title', array( $this, 'network_admin_page_title' ) );
67 add_filter( 'wp_stream_list_table_screen_id', array( $this, 'list_table_screen_id' ) );
68 add_filter( 'wp_stream_list_table_filters', array( $this, 'list_table_filters' ) );
69 add_filter( 'wp_stream_list_table_columns', array( $this, 'network_admin_columns' ) );
70 add_filter( 'wp_stream_settings_form_action', array( $this, 'settings_form_action' ) );
71 add_filter( 'wp_stream_settings_form_description', array( $this, 'settings_form_description' ) );
72 add_filter( 'wp_stream_settings_option_fields', array( $this, 'get_network_admin_fields' ) );
73 add_filter( 'wp_stream_serialized_labels', array( $this, 'get_settings_translations' ) );
74 add_filter( 'wp_stream_connectors', array( $this, 'hide_blogs_connector' ) );
75 }
76
77 /**
78 * Workaround to get admin-ajax.php to know when the request is from the Network Admin
79 *
80 * @return bool
81 *
82 * @action init
83 *
84 * @see https://core.trac.wordpress.org/ticket/22589
85 */
86 public function ajax_network_admin() {
87 $http_referer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '';
88
89 // Prefer filterable `wp_doing_ajax()` (WP 4.7+); the plugin supports 4.6.
90 $doing_ajax = function_exists( 'wp_doing_ajax' )
91 ? wp_doing_ajax()
92 : ( defined( 'DOING_AJAX' ) && DOING_AJAX );
93
94 if (
95 $doing_ajax
96 &&
97 0 === stripos( $http_referer, network_admin_url() )
98 &&
99 $this->can_view_network_records()
100 ) {
101 define( 'WP_NETWORK_ADMIN', true );
102 return WP_NETWORK_ADMIN;
103 }
104
105 return false;
106 }
107
108 /**
109 * Whether the current user is allowed to read records across the whole
110 * network (and to be treated as being in the Network Admin).
111 *
112 * The Referer prefix checked in ajax_network_admin() is caller-controlled,
113 * so it can only ever be a UI hint about where a request came from -- never
114 * a source of authority. Network-wide record access additionally requires a
115 * real network capability, otherwise a site-level Stream viewer could spoof
116 * the header to lift the per-blog restriction applied in
117 * network_query_args() or to have their actions logged against blog_id 0.
118 *
119 * @return bool
120 */
121 public function can_view_network_records() {
122 if ( ! is_multisite() ) {
123 return false;
124 }
125
126 // WP-CLI runs with shell-level access and usually with no logged-in
127 // user, so capability checks would fail for a legitimate operator and
128 // break `wp stream query --blog_id=N`. It sits outside this boundary.
129 if ( defined( 'WP_CLI' ) && WP_CLI ) {
130 return true;
131 }
132
133 return current_user_can( 'manage_network_options' );
134 }
135
136 /**
137 * Builds a stdClass object used when displaying actions done in network administration
138 *
139 * @return object
140 */
141 public function get_network_blog() {
142 $blog = new \stdClass();
143 $blog->blog_id = 0;
144 $blog->blogname = esc_html__( 'Network Admin', 'stream' );
145
146 return $blog;
147 }
148
149 /**
150 * Returns true if Stream is network activated, otherwise false
151 *
152 * @return bool
153 */
154 public function is_network_activated() {
155 return $this->plugin->is_network_activated();
156 }
157
158 /**
159 * Adds Stream to the admin bar under the "My Sites > Network Admin" menu
160 * if Stream has been network-activated.
161 *
162 * @action admin_bar_menu
163 *
164 * @param object $admin_bar Admin bar object.
165 *
166 * @return void
167 */
168 public function network_admin_bar_menu( $admin_bar ) {
169 if ( ! $this->is_network_activated() ) {
170 return;
171 }
172
173 $href = add_query_arg(
174 array(
175 'page' => $this->plugin->admin->records_page_slug,
176 ),
177 network_admin_url( $this->plugin->admin->admin_parent_page )
178 );
179
180 $admin_bar->add_menu(
181 array(
182 'id' => 'network-admin-stream',
183 'parent' => 'network-admin',
184 'title' => esc_html__( 'Stream', 'stream' ),
185 'href' => esc_url( $href ),
186 )
187 );
188 }
189
190 /**
191 * Add Network Settings and Default Settings menu pages
192 *
193 * @return array
194 */
195 public function admin_menu_screens() {
196 if ( ! is_network_admin() ) {
197 return;
198 }
199
200 remove_submenu_page( $this->plugin->admin->records_page_slug, 'wp_stream_settings' );
201 remove_submenu_page( $this->plugin->admin->records_page_slug, 'edit.php?post_type=wp_stream_alerts' );
202
203 $this->plugin->admin->screen_id['network_settings'] = add_submenu_page(
204 $this->plugin->admin->records_page_slug,
205 __( 'Stream Network Settings', 'stream' ),
206 __( 'Network Settings', 'stream' ),
207 $this->plugin->admin->settings_cap,
208 $this->network_settings_page_slug,
209 array( $this->plugin->admin, 'render_settings_page' )
210 );
211 }
212
213 /**
214 * Remove records when records TTL is shortened
215 *
216 * @param string $option_key Unused.
217 * @param array $new_value New value.
218 * @param array $old_value Old value.
219 *
220 * @action update_option_wp_stream
221 * @return void
222 */
223 public function updated_option_ttl_remove_records( $option_key, $new_value, $old_value ) {
224 unset( $option_key );
225 $this->plugin->settings->updated_option_ttl_remove_records( $old_value, $new_value );
226 }
227
228 /**
229 * Adjust the action of the settings form when in the Network Admin
230 *
231 * @param string $action Query string.
232 *
233 * @return string
234 */
235 public function settings_form_action( $action ) {
236 if ( is_network_admin() ) {
237 $current_page = wp_stream_filter_input( INPUT_GET, 'page' );
238 $action = add_query_arg(
239 array(
240 'action' => $current_page,
241 ),
242 'edit.php'
243 );
244 }
245
246 return $action;
247 }
248
249 /**
250 * Add a description to each of the Settings pages in the Network Admin
251 *
252 * @param string $description Description of the current page.
253 *
254 * @return string
255 */
256 public function settings_form_description( $description ) {
257 if ( ! is_network_admin() ) {
258 return '';
259 }
260
261 $current_page = wp_stream_filter_input( INPUT_GET, 'page' );
262
263 if ( $this->network_settings_page_slug === $current_page ) {
264 $description = __( 'These settings apply to all sites on the network.', 'stream' );
265 }
266
267 return $description;
268 }
269
270 /**
271 * Adjusts the settings fields displayed in various network admin screens
272 *
273 * @param array $fields Page settings fields.
274 *
275 * @return mixed
276 */
277 public function get_network_admin_fields( $fields ) {
278 if ( ! $this->is_network_activated() ) {
279 return $fields;
280 }
281
282 $stream_hidden_options = apply_filters(
283 'wp_stream_hidden_option_fields',
284 array(
285 'general' => array(
286 'records_ttl',
287 ),
288 'advanced' => array(
289 'delete_all_records',
290 ),
291 )
292 );
293
294 $network_hidden_options = apply_filters(
295 'wp_stream_network_option_fields',
296 array(
297 'general' => array(
298 'role_access',
299 ),
300 'exclude' => array(
301 'authors',
302 'roles',
303 'connectors',
304 'contexts',
305 'actions',
306 'ip_addresses',
307 'hide_previous_records',
308 ),
309 )
310 );
311
312 // Remove settings based on context.
313 if ( $this->plugin->settings->network_options_key === $this->plugin->settings->option_key ) {
314 $hidden_options = $network_hidden_options;
315 } else {
316 $hidden_options = $stream_hidden_options;
317 }
318
319 foreach ( $fields as $section_key => $section ) {
320 foreach ( $section['fields'] as $key => $field ) {
321 if ( ! isset( $hidden_options[ $section_key ] ) ) {
322 continue;
323 }
324
325 if ( in_array( $field['name'], $hidden_options[ $section_key ], true ) ) {
326 unset( $fields[ $section_key ]['fields'][ $key ] );
327 }
328 }
329 }
330
331 // Add settings based on context.
332 if ( $this->plugin->settings->network_options_key === $this->plugin->settings->option_key ) {
333 $new_fields['general']['fields'][] = array(
334 'name' => 'site_access',
335 'title' => __( 'Site Access', 'stream' ),
336 'after_field' => __( 'Enabled', 'stream' ),
337 'default' => 1,
338 'desc' => __( 'Allow sites on this network to view their Stream activity. Leave unchecked to only allow Stream to be viewed in the Network Admin.', 'stream' ),
339 'type' => 'checkbox',
340 );
341
342 $fields = array_merge_recursive( $new_fields, $fields );
343 }
344
345 // Remove empty settings sections.
346 foreach ( $fields as $section_key => $section ) {
347 if ( empty( $section['fields'] ) ) {
348 unset( $fields[ $section_key ] );
349 }
350 }
351
352 return $fields;
353 }
354
355 /**
356 * Get translations of serialized Stream Network settings
357 *
358 * @filter wp_stream_serialized_labels
359 *
360 * @param array $labels Setting labels.
361 *
362 * @return array Multidimensional array of fields
363 */
364 public function get_settings_translations( $labels ) {
365 $network_key = $this->plugin->settings->network_options_key;
366
367 if ( ! isset( $labels[ $network_key ] ) ) {
368 $labels[ $network_key ] = array();
369 }
370
371 foreach ( $this->plugin->settings->get_fields() as $section_slug => $section ) {
372 foreach ( $section['fields'] as $field ) {
373 $labels[ $network_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
374 }
375 }
376
377 return $labels;
378 }
379
380 /**
381 * Wrapper for the settings API to work on the network settings page
382 */
383 public function network_options_action() {
384
385 // Check the nonce.
386 if (
387 empty( $_POST['_wpnonce'] )
388 ||
389 ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), sprintf( '%s-options', $this->network_settings_option ) )
390 ) {
391 return;
392 }
393
394 // Check the user capability.
395 if ( ! current_user_can( $this->plugin->admin->settings_cap ) ) {
396 return;
397 }
398
399 // Check the action.
400 $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : '';
401 if ( $this->network_settings_page_slug !== $action ) {
402 return;
403 }
404
405 $option = ! empty( $_POST['option_page'] ) ? sanitize_key( wp_unslash( $_POST['option_page'] ) ) : false;
406
407 if ( $option && $this->network_settings_option === $option ) {
408
409 $value = array();
410 $posted_options = isset( $_POST[ $option ] ) && is_array( $_POST[ $option ] ) ? wp_unslash( $_POST[ $option ] ) : array();
411 $sections = $this->plugin->settings->get_fields();
412
413 foreach ( $sections as $section_name => $section ) {
414 foreach ( $section['fields'] as $field_idx => $field ) {
415 $option_key = $section_name . '_' . $field['name'];
416
417 if ( isset( $posted_options[ $option_key ] ) ) {
418 $value[ $option_key ] = $this->plugin->settings->sanitize_setting_by_field_type( $posted_options[ $option_key ], $field['type'] );
419 } else {
420 $value[ $option_key ] = false;
421 }
422 }
423 }
424
425 update_site_option( $this->network_settings_option, $value );
426 }
427
428 if ( ! count( get_settings_errors() ) ) {
429 add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'stream' ), 'updated' );
430 }
431
432 set_transient( 'settings_errors', get_settings_errors(), 30 );
433
434 $go_back = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
435
436 wp_safe_redirect( $go_back );
437
438 exit;
439 }
440
441 /**
442 * Add the Site filter to the Network records screen
443 *
444 * @filter wp_stream_list_table_filters
445 *
446 * @param array $filters Filters.
447 *
448 * @return array
449 */
450 public function list_table_filters( $filters ) {
451 if ( ! is_network_admin() || wp_is_large_network() ) {
452 return $filters;
453 }
454
455 $blogs = array();
456
457 // Display network blog as the first option.
458 $network_blog = $this->get_network_blog();
459
460 $blogs[ $network_blog->blog_id ] = array(
461 'label' => $network_blog->blogname,
462 'disabled' => '',
463 );
464
465 // Add all sites.
466 foreach ( wp_stream_get_sites() as $blog ) {
467 $blog_data = get_blog_details( $blog->blog_id );
468
469 $blogs[ $blog->blog_id ] = array(
470 'label' => $blog_data->blogname,
471 'disabled' => '',
472 );
473 }
474
475 $filters['blog_id'] = array(
476 'title' => __( 'sites', 'stream' ),
477 'items' => $blogs,
478 );
479
480 return $filters;
481 }
482
483 /**
484 * Add the Site toggle to screen options in network admin
485 *
486 * @param array $filters Filters.
487 *
488 * @return array
489 */
490 public function toggle_filters( $filters ) {
491 if ( is_network_admin() ) {
492 $filters['blog_id'] = esc_html__( 'Site', 'stream' );
493 }
494
495 return $filters;
496 }
497
498 /**
499 * Add the network suffix to the $screen_id when in the network admin
500 *
501 * @param int $screen_id Screen ID.
502 *
503 * @return string
504 */
505 public function list_table_screen_id( $screen_id ) {
506 if ( $screen_id && is_network_admin() ) {
507 if ( '-network' !== substr( $screen_id, -8 ) ) {
508 $screen_id .= '-network';
509 }
510 }
511
512 return $screen_id;
513 }
514
515 /**
516 * Set blog_id for network admin activity
517 *
518 * @param int $blog_id Blog ID.
519 *
520 * @return int
521 */
522 public function blog_id_logged( $blog_id ) {
523 return is_network_admin() ? 0 : $blog_id;
524 }
525
526 /**
527 * Customize query args on multisite installs
528 *
529 * @filter wp_stream_query_args
530 *
531 * @param array $args Site arguments.
532 *
533 * @return array
534 */
535 public function network_query_args( $args ) {
536 $args['site_id'] = is_numeric( $args['site_id'] ) ? $args['site_id'] : get_current_site()->id;
537
538 // Only users with a network capability may choose which blog to read
539 // from. For everyone else the requested blog_id is ignored entirely and
540 // forced to the current blog: a numeric type check is not an
541 // authorization check, and the Stream tables are shared across the
542 // whole network, so honouring an arbitrary ?blog_id= would let a
543 // site-level viewer read another site's activity.
544 if ( ! $this->can_view_network_records() ) {
545 $args['blog_id'] = get_current_blog_id();
546
547 return $args;
548 }
549
550 $args['blog_id'] = is_numeric( $args['blog_id'] ) ? $args['blog_id'] : ( is_network_admin() ? null : get_current_blog_id() );
551
552 return $args;
553 }
554
555 /**
556 * Add site count to the page title in the network admin
557 *
558 * @filter wp_stream_admin_page_title
559 *
560 * @param string $page_title Page title.
561 *
562 * @return string
563 */
564 public function network_admin_page_title( $page_title ) {
565 if ( is_network_admin() ) {
566 /* translators: %d: number of sites on the network (e.g. "42") */
567 $site_count = sprintf( _n( '%d site', '%d sites', get_blog_count(), 'stream' ), number_format( get_blog_count() ) );
568 $page_title = sprintf( '%s (%s)', $page_title, $site_count );
569 }
570
571 return $page_title;
572 }
573
574 /**
575 * Add the Site column to the network stream records
576 *
577 * @param array $columns Columns data.
578 *
579 * @return mixed
580 */
581 public function network_admin_columns( $columns ) {
582 if ( is_network_admin() || $this->ajax_network_admin() ) {
583 $columns = array_merge(
584 array_slice( $columns, 0, -1 ),
585 array(
586 'blog_id' => esc_html__( 'Site', 'stream' ),
587 ),
588 array_slice( $columns, -1 )
589 );
590 }
591
592 return $columns;
593 }
594
595 /**
596 * Prevent the Blogs connector from loading when not in Network Admin
597 *
598 * @param array $connectors Connectors.
599 *
600 * @return mixed
601 */
602 public function hide_blogs_connector( $connectors ) {
603 if ( ! is_network_admin() ) {
604 return array_diff( $connectors, array( 'Connector_Blogs' ) );
605 }
606
607 return $connectors;
608 }
609 }
610