<?php /** * @package Freemius * @copyright Copyright (c) 2015, Freemius, Inc. * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3 * @since 1.0.7 */ if ( ! defined( 'ABSPATH' ) ) { exit; } class FS_Admin_Notice_Manager { /** * @since 1.2.2 * * @var string */ protected $_module_unique_affix; /** * @var string */ protected $_id; /** * @var string */ protected $_title; /** * @var array[string]array */ private $_notices = array(); /** * @var FS_Key_Value_Storage */ private $_sticky_storage; /** * @var FS_Logger */ protected $_logger; /** * @since 2.0.0 * @var int The ID of the blog that is associated with the current site level admin notices. */ private $_blog_id = 0; /** * @since 2.0.0 * @var bool */ private $_is_network_notices; /** * @var FS_Admin_Notice_Manager[] */ private static $_instances = array(); /** * @param string $id * @param string $title * @param string $module_unique_affix * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on * network and blog admin pages. * @param bool $network_level_or_blog_id Since 2.0.0 * * @return \FS_Admin_Notice_Manager */ static function instance( $id, $title = '', $module_unique_affix = '', $is_network_and_blog_admins = false, $network_level_or_blog_id = false ) { if ( $is_network_and_blog_admins ) { $network_level_or_blog_id = true; } $key = strtolower( $id ); if ( is_multisite() ) { if ( true === $network_level_or_blog_id ) { $key .= ':ms'; } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Admin_Notice_Manager( $id, $title, $module_unique_affix, $is_network_and_blog_admins, $network_level_or_blog_id ); } return self::$_instances[ $key ]; } /** * @param string $id * @param string $title * @param string $module_unique_affix * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and * blog admin pages. * @param bool|int $network_level_or_blog_id */ protected function __construct( $id, $title = '', $module_unique_affix = '', $is_network_and_blog_admins = false, $network_level_or_blog_id = false ) { $this->_id = $id; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_title = ! empty( $title ) ? $title : ''; $this->_module_unique_affix = $module_unique_affix; $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); if ( is_multisite() ) { $this->_is_network_notices = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_network_notices = false; } $is_network_admin = fs_is_network_admin(); $is_blog_admin = fs_is_blog_admin(); if ( ( $this->_is_network_notices && $is_network_admin ) || ( ! $this->_is_network_notices && $is_blog_admin ) || ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) ) { if ( 0 < count( $this->_sticky_storage ) ) { $ajax_action_suffix = str_replace( ':', '-', $this->_id ); // If there are sticky notices for the current slug, add a callback // to the AJAX action that handles message dismiss. add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( &$this, 'dismiss_notice_ajax_callback' ) ); foreach ( $this->_sticky_storage as $msg ) { // Add admin notice. $this->add( $msg['message'], $msg['title'], $msg['type'], true, $msg['id'], false, isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, $is_network_and_blog_admins, isset( $msg['dismissible'] ) ? $msg['dismissible'] : null ); } } } } /** * Remove sticky message by ID. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * */ function dismiss_notice_ajax_callback() { check_admin_referer( 'fs_dismiss_notice_action' ); if ( ! is_numeric( $_POST['message_id'] ) ) { $this->_sticky_storage->remove( $_POST['message_id'] ); } wp_die(); } /** * Rendered sticky message dismiss JavaScript. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ static function _add_sticky_dismiss_javascript() { $params = array(); fs_require_once_template( 'sticky-admin-notice-js.php', $params ); } private static $_added_sticky_javascript = false; /** * Hook to the admin_footer to add sticky message dismiss JavaScript handler. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ private static function has_sticky_messages() { if ( ! self::$_added_sticky_javascript ) { add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); } } /** * Handle admin_notices by printing the admin messages stacked in the queue. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * */ function _admin_notices_hook() { if ( function_exists( 'current_user_can' ) && ! current_user_can( 'manage_options' ) ) { // Only show messages to admins. return; } foreach ( $this->_notices as $id => $msg ) { if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { if ( get_current_user_id() != $msg['wp_user_id'] ) { continue; } } /** * Added a filter to control the visibility of admin notices. * * Usage example: * * /** * * @param bool $show * * @param array $msg { * * @var string $message The actual message. * * @var string $title An optional message title. * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). * * @var string $id The unique identifier of the message. * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `<slug>-theme`. * * @var string $plugin The product's title. * * @var string $wp_user_id An optional WP user ID that this admin notice is for. * * } * * * * @return bool * *\/ * function my_custom_show_admin_notice( $show, $msg ) { * if ('trial_promotion' != $msg['id']) { * return false; * } * * return $show; * } * * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); * * @author Vova Feldman * @since 2.2.0 */ $show_notice = call_user_func_array( 'fs_apply_filter', array( $this->_module_unique_affix, 'show_admin_notice', $this->show_admin_notices(), $msg ) ); if ( true !== $show_notice ) { continue; } fs_require_template( 'admin-notice.php', $msg ); if ( $msg['sticky'] ) { self::has_sticky_messages(); } } } /** * Enqueue common stylesheet to style admin notice. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _enqueue_styles() { fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); } /** * Check if the current page is the Gutenberg block editor. * * @author Vova Feldman (@svovaf) * @since 2.2.3 * * @return bool */ function is_gutenberg_page() { if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) { // The Gutenberg plugin is on. return true; } $current_screen = get_current_screen(); if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) { // Gutenberg page on 5+. return true; } return false; } /** * Check if admin notices should be shown on page. E.g., we don't want to show notices in the Visual Editor. * * @author Xiaheng Chen (@xhchen) * @since 2.4.2 * * @return bool */ function show_admin_notices() { global $pagenow; if ( 'about.php' === $pagenow ) { // Don't show admin notices on the About page. return false; } if ( $this->is_gutenberg_page() ) { // Don't show admin notices in Gutenberg (visual editor). return false; } return true; } /** * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID * @param bool $store_if_sticky * @param number|null $wp_user_id * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network * and blog admin pages. * @param bool|null $is_dismissible * @param array $data * * @uses add_action() */ function add( $message, $title = '', $type = 'success', $is_sticky = false, $id = '', $store_if_sticky = true, $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dismissible = null, $data = array() ) { $notices_type = $this->get_notices_type(); if ( empty( $this->_notices ) ) { if ( ! $is_network_and_blog_admins ) { add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); } else { add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); } add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); } if ( '' === $id ) { $id = md5( $title . ' ' . $message . ' ' . $type ); } $message_object = array( 'message' => $message, 'title' => $title, 'type' => $type, 'sticky' => $is_sticky, 'id' => $id, 'manager_id' => $this->_id, 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), 'wp_user_id' => $wp_user_id, 'dismissible' => $is_dismissible, 'data' => $data ); if ( $is_sticky && $store_if_sticky ) { $this->_sticky_storage->{$id} = $message_object; } $this->_notices[ $id ] = $message_object; } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string|string[] $ids * @param bool $store */ function remove_sticky( $ids, $store = true ) { if ( ! is_array( $ids ) ) { $ids = array( $ids ); } foreach ( $ids as $id ) { // Remove from sticky storage. $this->_sticky_storage->remove( $id, $store ); if ( isset( $this->_notices[ $id ] ) ) { unset( $this->_notices[ $id ] ); } } } /** * Check if sticky message exists by id. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param $id * * @return bool */ function has_sticky( $id ) { return isset( $this->_sticky_storage[ $id ] ); } /** * Adds sticky admin notification. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $message * @param string $id Message ID * @param string $title * @param string $type * @param number|null $wp_user_id * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network * and blog admin pages. * @param bool $is_dimissible * @param array $data */ function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dimissible = true, $data = array() ) { if ( ! empty( $this->_module_unique_affix ) ) { $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); } $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dimissible, $data ); } /** * Retrieves the data of an sticky notice. * * @author Leo Fajardo (@leorw) * @since 2.4.3 * * @param string $id Message ID. * * @return array|null */ function get_sticky( $id ) { return isset( $this->_sticky_storage->{$id} ) ? $this->_sticky_storage->{$id} : null; } /** * Clear all sticky messages. * * @author Vova Feldman (@svovaf) * @since 1.0.8 * * @param bool $is_temporary @since 2.5.1 */ function clear_all_sticky( $is_temporary = false ) { if ( $is_temporary ) { $this->_notices = array(); } else { $this->_sticky_storage->clear_all(); } } #-------------------------------------------------------------------------------- #region Helper Method #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ private function get_notices_type() { return $this->_is_network_notices ? 'network_admin_notices' : 'admin_notices'; } #endregion }