plugin = $plugin; add_action( 'init', array( $this, 'init' ) ); // Ensure function used in various methods is pre-loaded. if ( ! function_exists( 'is_plugin_active_for_network' ) ) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } // User and role caps. add_filter( 'user_has_cap', array( $this, 'filter_user_caps' ), 10, 4 ); add_filter( 'role_has_cap', array( $this, 'filter_role_caps' ), 10, 3 ); if ( is_multisite() && $plugin->is_network_activated() && ! is_network_admin() ) { $options = (array) get_site_option( 'wp_stream_network', array() ); $option = isset( $options['general_site_access'] ) ? absint( $options['general_site_access'] ) : 1; $this->disable_access = ( $option ) ? false : true; } // Register settings page. if ( ! $this->disable_access ) { add_action( 'admin_menu', array( $this, 'register_menu' ) ); } // Admin notices. add_action( 'admin_notices', array( $this, 'prepare_admin_notices' ) ); add_action( 'shutdown', array( $this, 'admin_notices' ) ); // Add admin body class. add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); // Plugin action links. add_filter( 'plugin_action_links', array( $this, 'plugin_action_links', ), 10, 2 ); // Load admin scripts and styles. add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts', ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) ); // Reset Streams database. add_action( 'wp_ajax_wp_stream_reset', array( $this, 'wp_ajax_reset', ) ); /** * Uninstall Streams and Deactivate plugin. * * @todo Confirm if variable assignment is necessary. */ $uninstall = $this->plugin->db->driver->purge_storage( $this->plugin ); // Auto purge setup. add_action( 'wp_loaded', array( $this, 'purge_schedule_setup' ) ); add_action( 'wp_stream_auto_purge', array( $this, 'purge_scheduled_action', ) ); // Ajax users list. add_action( 'wp_ajax_wp_stream_filters', array( $this, 'ajax_filters', ) ); } /** * Load admin classes * * @action init */ public function init() { $this->network = new Network( $this->plugin ); $this->live_update = new Live_Update( $this->plugin ); $this->export = new Export( $this->plugin ); } /** * Output specific updates passed as URL parameters. * * @action admin_notices * * @return void */ public function prepare_admin_notices() { $message = wp_stream_filter_input( INPUT_GET, 'message' ); switch ( $message ) { case 'settings_reset': $this->notice( esc_html__( 'All site settings have been successfully reset.', 'stream' ) ); break; } } /** * Handle notice messages according to the appropriate context (WP-CLI or the WP Admin) * * @param string $message Message to output. * @param bool $is_error If the message is error_level (true) or warning (false). */ public function notice( $message, $is_error = true ) { if ( defined( 'WP_CLI' ) && WP_CLI ) { $message = wp_strip_all_tags( $message ); if ( $is_error ) { WP_CLI::warning( $message ); } else { WP_CLI::success( $message ); } } else { // Trigger admin notices late, so that any notices which occur during page load are displayed. add_action( 'shutdown', array( $this, 'admin_notices' ) ); $notice = compact( 'message', 'is_error' ); if ( ! in_array( $notice, $this->notices, true ) ) { $this->notices[] = $notice; } } } /** * Show an error or other message in the WP Admin * * @action shutdown */ public function admin_notices() { global $allowedposttags; $custom = array( 'progress' => array( 'class' => true, 'id' => true, 'max' => true, 'style' => true, 'value' => true, ), ); $allowed_html = array_merge( $allowedposttags, $custom ); ksort( $allowed_html ); foreach ( $this->notices as $notice ) { $class_name = empty( $notice['is_error'] ) ? 'updated' : 'error'; $html_message = sprintf( '
%s
', esc_attr( $class_name ), wpautop( $notice['message'] ) ); echo wp_kses( $html_message, $allowed_html ); } } /** * Register menu page * * @action admin_menu * * @return void */ public function register_menu() { /** * Filter the main admin menu title * * @return string */ $main_menu_title = apply_filters( 'wp_stream_admin_menu_title', esc_html__( 'Stream', 'stream' ) ); /** * Filter the main admin menu position * * Note: Using longtail decimal string to reduce the chance of position conflicts, see Codex * * @return string */ $main_menu_position = apply_filters( 'wp_stream_menu_position', '2.999999' ); /** * Filter the main admin page title * * @return string */ $main_page_title = apply_filters( 'wp_stream_admin_page_title', esc_html__( 'Stream Records', 'stream' ) ); $this->screen_id['main'] = add_menu_page( $main_page_title, $main_menu_title, $this->view_cap, $this->records_page_slug, array( $this, 'render_list_table' ), 'div', $main_menu_position ); /** * Fires before submenu items are added to the Stream menu * allowing plugins to add menu items before Settings * * @return void */ do_action( 'wp_stream_admin_menu' ); /** * Filter the Settings admin page title * * @return string */ $settings_page_title = apply_filters( 'wp_stream_settings_form_title', esc_html__( 'Stream Settings', 'stream' ) ); $this->screen_id['settings'] = add_submenu_page( $this->records_page_slug, $settings_page_title, esc_html__( 'Settings', 'stream' ), $this->settings_cap, $this->settings_page_slug, array( $this, 'render_settings_page' ) ); if ( isset( $this->screen_id['main'] ) ) { /** * Fires just before the Stream list table is registered. * * @return void */ do_action( 'wp_stream_admin_menu_screens' ); // Register the list table early, so it associates the column headers with 'Screen settings'. add_action( 'load-' . $this->screen_id['main'], array( $this, 'register_list_table', ) ); } } /** * Enqueue scripts/styles for admin screen * * @action admin_enqueue_scripts * * @param string $hook Current hook. * * @return void */ public function admin_enqueue_scripts( $hook ) { wp_register_script( 'wp-stream-select2', $this->plugin->locations['url'] . 'ui/lib/select2/js/select2.full.min.js', array( 'jquery' ), '3.5.2', true ); wp_register_style( 'wp-stream-select2', $this->plugin->locations['url'] . 'ui/lib/select2/css/select2.min.css', array(), '3.5.2' ); wp_register_script( 'wp-stream-timeago', $this->plugin->locations['url'] . 'ui/lib/timeago/jquery.timeago.js', array(), '1.4.1', true ); $locale = strtolower( substr( get_locale(), 0, 2 ) ); $file_tmpl = 'ui/lib/timeago/locales/jquery.timeago.%s.js'; if ( file_exists( $this->plugin->locations['dir'] . sprintf( $file_tmpl, $locale ) ) ) { wp_register_script( 'wp-stream-timeago-locale', $this->plugin->locations['url'] . sprintf( $file_tmpl, $locale ), array( 'wp-stream-timeago' ), '1', false ); } else { wp_register_script( 'wp-stream-timeago-locale', $this->plugin->locations['url'] . sprintf( $file_tmpl, 'en' ), array( 'wp-stream-timeago' ), '1', false ); } $min = wp_stream_min_suffix(); wp_enqueue_style( 'wp-stream-admin', $this->plugin->locations['url'] . 'ui/css/admin.' . $min . 'css', array(), $this->plugin->get_version() ); $script_screens = array( 'plugins.php' ); if ( in_array( $hook, $this->screen_id, true ) || in_array( $hook, $script_screens, true ) ) { wp_enqueue_script( 'wp-stream-select2' ); wp_enqueue_style( 'wp-stream-select2' ); wp_enqueue_script( 'wp-stream-timeago' ); wp_enqueue_script( 'wp-stream-timeago-locale' ); wp_enqueue_script( 'wp-stream-admin', $this->plugin->locations['url'] . 'ui/js/admin.' . $min . 'js', array( 'jquery', 'wp-stream-select2', ), $this->plugin->get_version(), false ); wp_enqueue_script( 'wp-stream-admin-exclude', $this->plugin->locations['url'] . 'ui/js/exclude.' . $min . 'js', array( 'jquery', 'wp-stream-select2', ), $this->plugin->get_version(), false ); wp_enqueue_script( 'wp-stream-live-updates', $this->plugin->locations['url'] . 'ui/js/live-updates.' . $min . 'js', array( 'jquery', 'heartbeat', ), $this->plugin->get_version(), false ); wp_localize_script( 'wp-stream-admin', 'wp_stream', array( 'i18n' => array( 'confirm_purge' => esc_html__( 'Are you sure you want to delete all Stream activity records from the database? This cannot be undone.', 'stream' ), 'confirm_defaults' => esc_html__( 'Are you sure you want to reset all site settings to default? This cannot be undone.', 'stream' ), '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' ), ), 'locale' => esc_js( $locale ), 'gmt_offset' => get_option( 'gmt_offset' ), ) ); $order_types = array( 'asc', 'desc' ); wp_localize_script( 'wp-stream-live-updates', 'wp_stream_live_updates', array( 'current_screen' => $hook, 'current_page' => isset( $_GET['paged'] ) ? absint( wp_unslash( $_GET['paged'] ) ) : '1', // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'current_order' => isset( $_GET['order'] ) && in_array( strtolower( $_GET['order'] ), $order_types, true ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended ? esc_js( $_GET['order'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended : 'desc', 'current_query' => wp_stream_json_encode( $_GET ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'current_query_count' => count( $_GET ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended ) ); } /** * The maximum number of items that can be updated in bulk without receiving a warning. * * Stream watches for bulk actions performed in the WordPress Admin (such as updating * many posts at once) and warns the user before proceeding if the number of items they * are attempting to update exceeds this threshold value. Since Stream will try to save * a log for each item, it will take longer than usual to complete the operation. * * The default threshold is 100 items. * * @return int */ $bulk_actions_threshold = apply_filters( 'wp_stream_bulk_actions_threshold', 100 ); wp_enqueue_script( 'wp-stream-global', $this->plugin->locations['url'] . 'ui/js/global.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version(), false ); wp_localize_script( 'wp-stream-global', 'wp_stream_global', array( 'bulk_actions' => array( 'i18n' => array( /* translators: %s: a number of items (e.g. "1,742") */ '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 ) ) ), ), 'threshold' => absint( $bulk_actions_threshold ), ), 'plugins_screen_url' => self_admin_url( 'plugins.php#stream' ), ) ); } /** * Check whether or not the current admin screen belongs to Stream * * @return bool */ public function is_stream_screen() { if ( is_admin() && false !== strpos( wp_stream_filter_input( INPUT_GET, 'page' ), $this->records_page_slug ) ) { return true; } $screen = get_current_screen(); if ( is_admin() && Alerts::POST_TYPE === $screen->post_type ) { return true; } return false; } /** * Add a specific body class to all Stream admin screens * * @param string $classes CSS classes to output to body. * * @filter admin_body_class * * @return string */ public function admin_body_class( $classes ) { $stream_classes = array(); if ( $this->is_stream_screen() ) { $stream_classes[] = $this->admin_body_class; if ( isset( $_GET['page'] ) ) { // // phpcs:ignore WordPress.Security.NonceVerification.Recommended $stream_classes[] = sanitize_key( $_GET['page'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } } /** * Filter the Stream admin body classes * * @return array */ $stream_classes = apply_filters( 'wp_stream_admin_body_classes', $stream_classes ); $stream_classes = implode( ' ', array_map( 'trim', $stream_classes ) ); return sprintf( '%s %s ', $classes, $stream_classes ); } /** * Add menu styles for various WP Admin skins * * @uses \wp_add_inline_style() * * @action admin_enqueue_scripts */ public function admin_menu_css() { $min = wp_stream_min_suffix(); wp_register_style( 'wp-stream-datepicker', $this->plugin->locations['url'] . 'ui/css/datepicker.' . $min . 'css', array(), $this->plugin->get_version() ); wp_register_style( 'wp-stream-icons', $this->plugin->locations['url'] . 'ui/stream-icons/style.css', array(), $this->plugin->get_version() ); // Make sure we're working off a clean version. if ( ! file_exists( ABSPATH . WPINC . '/version.php' ) ) { return; } include ABSPATH . WPINC . '/version.php'; if ( ! isset( $wp_version ) ) { return; } $body_class = $this->admin_body_class; $records_page = $this->records_page_slug; $stream_url = $this->plugin->locations['url']; if ( version_compare( $wp_version, '3.8-alpha', '>=' ) ) { wp_enqueue_style( 'wp-stream-icons' ); $css = " #toplevel_page_{$records_page} .wp-menu-image:before { font-family: 'WP Stream' !important; content: '\\73' !important; } #toplevel_page_{$records_page} .wp-menu-image { background-repeat: no-repeat; } #menu-posts-feedback .wp-menu-image:before { font-family: dashicons !important; content: '\\f175'; } #adminmenu #menu-posts-feedback div.wp-menu-image { background: none !important; background-repeat: no-repeat; } body.{$body_class} #wpbody-content .wrap h1:nth-child(1):before { font-family: 'WP Stream' !important; content: '\\73'; padding: 0 8px 0 0; } "; } else { $css = " #toplevel_page_{$records_page} .wp-menu-image { background: url( {$stream_url}ui/stream-icons/menuicon-sprite.png ) 0 90% no-repeat; } /* Retina Stream Menu Icon */ @media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) { #toplevel_page_{$records_page} .wp-menu-image { background: url( {$stream_url}ui/stream-icons/menuicon-sprite-2x.png ) 0 90% no-repeat; background-size:30px 64px; } } #toplevel_page_{$records_page}.current .wp-menu-image, #toplevel_page_{$records_page}.wp-has-current-submenu .wp-menu-image, #toplevel_page_{$records_page}:hover .wp-menu-image { background-position: top left; } "; } \wp_add_inline_style( 'wp-admin', $css ); } /** * Handle the reset AJAX request to reset logs. * * @return bool */ public function wp_ajax_reset() { check_ajax_referer( 'stream_nonce_reset', 'wp_stream_nonce_reset' ); if ( ! current_user_can( $this->settings_cap ) ) { wp_die( esc_html__( "You don't have sufficient privileges to do this action.", 'stream' ) ); } $this->erase_stream_records(); if ( defined( 'WP_STREAM_TESTS' ) && WP_STREAM_TESTS ) { return true; } wp_safe_redirect( add_query_arg( array( 'page' => is_network_admin() ? $this->network->network_settings_page_slug : $this->settings_page_slug, 'message' => 'data_erased', ), self_admin_url( $this->admin_parent_page ) ) ); exit; } /** * Clears stream records from the database. * * @return void */ private function erase_stream_records() { global $wpdb; $where = ''; if ( is_multisite() && ! $this->plugin->is_network_activated() ) { $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() ); } $wpdb->query( "DELETE `stream`, `meta` FROM {$wpdb->stream} AS `stream` LEFT JOIN {$wpdb->streammeta} AS `meta` ON `meta`.`record_id` = `stream`.`ID` WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared ); } /** * Schedules a purge of records. * * @return void */ public function purge_schedule_setup() { if ( ! wp_next_scheduled( 'wp_stream_auto_purge' ) ) { wp_schedule_event( time(), 'twicedaily', 'wp_stream_auto_purge' ); } } /** * Executes a scheduled purge * * @return void */ public function purge_scheduled_action() { global $wpdb; // Don't purge when in Network Admin unless Stream is network activated. if ( is_multisite() && is_network_admin() && ! $this->plugin->is_network_activated() ) { return; } $defaults = $this->plugin->settings->get_defaults(); if ( is_multisite() && $this->plugin->is_network_activated() ) { $options = (array) get_site_option( 'wp_stream_network', $defaults ); } else { $options = (array) get_option( 'wp_stream', $defaults ); } if ( ! empty( $options['general_keep_records_indefinitely'] ) || ! isset( $options['general_records_ttl'] ) ) { return; } $days = $options['general_records_ttl']; $timezone = new DateTimeZone( 'UTC' ); $date = new DateTime( 'now', $timezone ); $date->sub( DateInterval::createFromDateString( "$days days" ) ); $where = $wpdb->prepare( ' AND `stream`.`created` < %s', $date->format( 'Y-m-d H:i:s' ) ); // Multisite but NOT network activated, only purge the current blog. if ( is_multisite() && ! $this->plugin->is_network_activated() ) { $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() ); } $wpdb->query( "DELETE `stream`, `meta` FROM {$wpdb->stream} AS `stream` LEFT JOIN {$wpdb->streammeta} AS `meta` ON `meta`.`record_id` = `stream`.`ID` WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared ); } /** * Returns the admin action links. * * @filter plugin_action_links * * @param array $links Action links. * @param string $file Plugin file. * * @return array */ public function plugin_action_links( $links, $file ) { if ( plugin_basename( $this->plugin->locations['dir'] . 'stream.php' ) !== $file ) { return $links; } // Also don't show links in Network Admin if Stream isn't network enabled. if ( is_network_admin() && is_multisite() && ! $this->plugin->is_network_activated() ) { return $links; } if ( is_network_admin() ) { $admin_page_url = add_query_arg( array( 'page' => $this->network->network_settings_page_slug, ), network_admin_url( $this->admin_parent_page ) ); } else { $admin_page_url = add_query_arg( array( 'page' => $this->settings_page_slug, ), admin_url( $this->admin_parent_page ) ); } $links[] = sprintf( '%s', esc_url( $admin_page_url ), esc_html__( 'Settings', 'default' ) ); if ( ! defined( 'DISALLOW_FILE_MODS' ) || false === DISALLOW_FILE_MODS ) { $url = add_query_arg( array( 'action' => 'wp_stream_uninstall', 'wp_stream_nonce' => wp_create_nonce( 'stream_nonce' ), ), admin_url( 'admin-ajax.php' ) ); $links[] = sprintf( '%s', esc_url( $url ), esc_html__( 'Uninstall', 'stream' ) ); } return $links; } /** * Render main page */ public function render_list_table() { $this->list_table->prepare_items(); ?>

list_table->display(); ?>
plugin->settings->option_key; $form_action = apply_filters( 'wp_stream_settings_form_action', admin_url( 'options.php' ) ); $page_description = apply_filters( 'wp_stream_settings_form_description', '' ); $sections = $this->plugin->settings->get_fields(); $active_tab = wp_stream_filter_input( INPUT_GET, 'tab' ); $min = wp_stream_min_suffix(); wp_enqueue_script( 'wp-stream-settings', $this->plugin->locations['url'] . 'ui/js/settings.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version(), true ); ?>

1 ) : ?>
list_table = new List_Table( $this->plugin, array( 'screen' => $this->screen_id['main'], ) ); } /** * Check if a particular role has access * * @param string $role User role. * * @return bool */ private function role_can_view( $role ) { if ( in_array( $role, $this->plugin->settings->options['general_role_access'], true ) ) { return true; } return false; } /** * Filter user caps to dynamically grant our view cap based on allowed roles * * @param array $allcaps All capabilities. * @param array $caps Required caps. * @param array $args Unused. * @param WP_User $user User. * * @filter user_has_cap * * @return array */ public function filter_user_caps( $allcaps, $caps, $args, $user = null ) { global $wp_roles; $_wp_roles = isset( $wp_roles ) ? $wp_roles : new WP_Roles(); $user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user(); // @see // https://github.com/WordPress/WordPress/blob/c67c9565f1495255807069fdb39dac914046b1a0/wp-includes/capabilities.php#L758 $roles = array_unique( array_merge( $user->roles, array_filter( array_keys( $user->caps ), array( $_wp_roles, 'is_role' ) ) ) ); $stream_view_caps = array( $this->view_cap ); foreach ( $caps as $cap ) { if ( in_array( $cap, $stream_view_caps, true ) ) { foreach ( $roles as $role ) { if ( $this->role_can_view( $role ) ) { $allcaps[ $cap ] = true; break 2; } } } } return $allcaps; } /** * Filter role caps to dynamically grant our view cap based on allowed roles * * @filter role_has_cap * * @param array $allcaps All capabilities. * @param string $cap Require cap. * @param string $role User role. * * @return array */ public function filter_role_caps( $allcaps, $cap, $role ) { $stream_view_caps = array( $this->view_cap ); if ( in_array( $cap, $stream_view_caps, true ) && $this->role_can_view( $role ) ) { $allcaps[ $cap ] = true; } return $allcaps; } /** * Ajax callback for return a user list. * * @action wp_ajax_wp_stream_filters */ public function ajax_filters() { if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) { wp_die( '-1' ); } check_ajax_referer( 'stream_filters_user_search_nonce', 'nonce' ); switch ( wp_stream_filter_input( INPUT_GET, 'filter' ) ) { case 'user_id': $users = array_merge( array( 0 => (object) array( 'display_name' => 'WP-CLI', ), ), get_users() ); $search = wp_stream_filter_input( INPUT_GET, 'q' ); if ( $search ) { // `search` arg for get_users() is not enough $users = array_filter( $users, function ( $user ) use ( $search ) { return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( $search ) ); } ); } if ( count( $users ) > $this->preload_users_max ) { $users = array_slice( $users, 0, $this->preload_users_max ); } // Get gravatar / roles for final result set. $results = $this->get_users_record_meta( $users ); break; } if ( isset( $results ) ) { echo wp_stream_json_encode( $results ); // xss ok. } die(); } /** * Return relevant user meta data. * * @param array $authors Author data. * @return array */ public function get_users_record_meta( $authors ) { $authors_records = array(); foreach ( $authors as $user_id => $args ) { $author = new Author( $args->ID ); $authors_records[ $user_id ] = array( 'text' => $author->get_display_name(), 'id' => $author->id, 'label' => $author->get_display_name(), 'icon' => $author->get_avatar_src( 32 ), 'title' => '', ); } return $authors_records; } /** * Get user meta in a way that is also safe for VIP * * @param int $user_id User ID. * @param string $meta_key Meta key. * @param bool $single Return first found meta value connected to the meta key (optional). * * @return mixed */ public function get_user_meta( $user_id, $meta_key, $single = true ) { if ( wp_stream_is_vip() && function_exists( 'get_user_attribute' ) ) { return get_user_attribute( $user_id, $meta_key ); } return get_user_meta( $user_id, $meta_key, $single ); } /** * Update user meta in a way that is also safe for VIP * * @param int $user_id User ID. * @param string $meta_key Meta key. * @param mixed $meta_value Meta value. * @param mixed $prev_value Previous meta value being overwritten (optional). * * @return int|bool */ public function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) { if ( wp_stream_is_vip() && function_exists( 'update_user_attribute' ) ) { return update_user_attribute( $user_id, $meta_key, $meta_value ); } return update_user_meta( $user_id, $meta_key, $meta_value, $prev_value ); } /** * Delete user meta in a way that is also safe for VIP * * @param int $user_id User ID. * @param string $meta_key Meta key. * @param mixed $meta_value Meta value (optional). * * @return bool */ public function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) { if ( wp_stream_is_vip() && function_exists( 'delete_user_attribute' ) ) { return delete_user_attribute( $user_id, $meta_key, $meta_value ); } return delete_user_meta( $user_id, $meta_key, $meta_value ); } }