: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Plugin notices handler.
* This class will take care of registering, queuing and showing different
* notices across WP pages.
* @author Incsub (Joel James)
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @copyright Copyright (c) 2022, Incsub
* @package WPMUDEV\Notices
namespace WPMUDEV\Notices;
// If this file is called directly, abort.
defined( 'WPINC' ) || die;
if ( ! class_exists( __NAMESPACE__ . '\\Handler' ) ) {
* @package WPMUDEV\Notices
public $version = '2.0.5';
* Option name to store data.
* @var string $option_name
protected $option_name = 'wpmudev_notices';
* Registered plugins for the opt-ins.
private $plugins = array();
* WordPress screen IDs to show notices on.
private $screens = array();
* @var string[] $disabled
private $disabled = array( 'email', 'giveaway' );
* Registered plugin notices data from db.
* Notice types that are shown on WP Dashboard.
private $wp_notices = array(
'email' => '\WPMUDEV\Notices\Notices\Email',
'rate' => '\WPMUDEV\Notices\Notices\Rating',
* Notice type that are shown within plugin pages.
* @var array $plugin_notices
private $plugin_notices = array(
'giveaway' => '\WPMUDEV\Notices\Notices\Giveaway',
* Construct handler class.
protected function __construct() {
add_action( 'wpmudev_register_notices', array( $this, 'register' ), 10, 2 );
// Always setup ajax actions.
add_action( 'wp_ajax_wpmudev_notices_action', array( $this, 'process_action' ) );
add_action( 'load-index.php', array( $this, 'admin_notice' ) );
* Initializes and returns the singleton instance.
public static function instance() {
if ( null === $instance ) {
* Register an active plugin for notices.
* 'wpmudev_register_notices',
* 'beehive', // Plugin id.
* 'basename' => plugin_basename( BEEHIVE_PLUGIN_FILE ), // Required: Plugin basename (for backward compat).
* 'title' => 'Beehive', // Required: Plugin title.
* 'wp_slug' => 'beehive-analytics', // Required: wp.org slug of the plugin.
* 'cta_email' => __( 'Get Fast!', 'ga_trans' ), // Email button CTA.
* 'installed_on' => time(), // Optional: Plugin activated time.
* 'screens' => array( // Required: Plugin screen ids.
* 'toplevel_page_beehive',
* 'dashboard_page_beehive-accounts',
* 'dashboard_page_beehive-settings',
* 'dashboard_page_beehive-tutorials',
* 'dashboard_page_beehive-google-analytics',
* 'dashboard_page_beehive-google-tag-manager',
* @param array $options Options.
* @param string $plugin_id Plugin ID.
public function register( $plugin_id, array $options = array() ) {
// Plugin ID can't be empty.
if ( empty( $plugin_id ) ) {
// Add to the plugins list.
$this->plugins[ $plugin_id ] = $options;
if ( ! empty( $options['screens'] ) ) {
$this->add_to_screens( $plugin_id, $options['screens'] );
* Show an admin notice on WP page (not plugin's SUI pages).
public function admin_notice() {
if ( is_super_admin() ) {
add_action( 'all_admin_notices', array( $this, 'render_admin_notice' ) );
* Show a notice on current plugin page.
public function plugin_notice() {
if ( is_super_admin() ) {
add_action( 'all_admin_notices', array( $this, 'render_plugin_notice' ) );
* Render admin notice content.
public function render_admin_notice() {
$this->render( false, array_keys( $this->wp_notices ) );
* Render a plugin notice content.
public function render_plugin_notice() {
$screen = get_current_screen();
// Continue only if registered screen.
if ( empty( $screen->id ) || empty( $this->screens[ $screen->id ] ) ) {
$this->screens[ $screen->id ],
array_keys( $this->plugin_notices )
* Process a notice action.
* All ajax requests from the notice are processed here.
* After nonce verification the action will be processed if a matching
* method is already defined.
public function process_action() {
// Check required fields.
if ( ! isset( $_POST['plugin_id'], $_POST['notice_action'], $_POST['notice_type'], $_POST['nonce'] ) ) {
wp_die( esc_html__( 'Required fields are missing.', 'wdev_frash' ) );
// Only admins can do this.
if ( ! is_super_admin() ) {
wp_die( esc_html__( 'Access check failed.', 'wdev_frash' ) );
$plugin = sanitize_text_field( wp_unslash( $_POST['plugin_id'] ) );
$action = sanitize_text_field( wp_unslash( $_POST['notice_action'] ) );
$type = sanitize_text_field( wp_unslash( $_POST['notice_type'] ) );
$nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );
if ( ! wp_verify_nonce( $nonce, 'wpmudev_notices_action' ) ) {
wp_die( esc_html__( 'Nonce verification failed.', 'wdev_frash' ) );
// Initialize the options.
$notice = $this->get_notice( $plugin, $type );
// Process action if defined on this class.
if ( method_exists( $this, $action ) ) {
call_user_func( array( $this, $action ), $plugin, $type );
} elseif ( is_object( $notice ) && method_exists( $notice, $action ) ) {
// Process action if defined on the notice class.
call_user_func( array( $notice, $action ), $plugin );
* Action hook to do something after a notice action is performed.
* @param string $plugin Plugin ID.
* @param string $type Notice type.
* @param string $action Action.
do_action( 'wpmudev_notices_after_notice_action', $action, $plugin, $type );
* Remove a notice from the queue.
* @param string $type Notice type.
* @param string $plugin Plugin ID.
public function dismiss_notice( $plugin, $type ) {
// Remove from the queue.
if ( isset( $this->stored['queue'][ $plugin ][ $type ] ) ) {
unset( $this->stored['queue'][ $plugin ][ $type ] );
if ( ! isset( $this->stored['done'][ $plugin ] ) ) {
$this->stored['done'][ $plugin ] = array();
$this->stored['done'][ $plugin ][ $type ] = time();
* Getter for the queue data.
public function get_option() {
* Update the notices stored data in db.
* @param array $data Option data (optional).
public function update_option( $data = false ) {
// If new data is provided use it.
if ( ! empty( $data ) ) {
return update_site_option( $this->option_name, $this->stored );
* Render notice for the current screen.
* @param array $types Notice types to render.
* @param string|false $plugin_id Plugin id (false to check all plugins).
protected function render( $plugin_id = false, $types = array() ) {
// Setup queue when required.
if ( empty( $plugin_id ) ) {
$notice = $this->get_random_notice( $types, $plugin_id );
// Get a plugin's notice.
$notice = $this->get_plugin_notice( $plugin_id, $types );
// Render if notice found.
if ( ! empty( $notice ) && method_exists( $notice, 'render' ) ) {
return call_user_func( array( $notice, 'render' ), $plugin_id );
* Set screen IDs for the notices.
* NOTE: Only one plugin can use one screen id.
* @param array $screens Screen IDs.
* @param string $plugin_id Plugin ID.
protected function add_to_screens( $plugin_id, array $screens ) {
if ( ! empty( $screens ) ) {
foreach ( $screens as $screen_id ) {
$this->screens[ $screen_id ] = $plugin_id;
// Remove network suffix for page hook.
$screen_id = str_replace( '-network', '', $screen_id );
// Register screen notice.
add_action( "load-$screen_id", array( $this, 'plugin_notice' ) );
* Setup the notices queue when ready.
* To avoid calling db queries we need to do this only before
* a notice is being rendered.
protected function setup_queue() {
// Setup all registered plugins to in queue.
foreach ( $this->plugins as $plugin_id => $options ) {
$this->add_to_queue( $plugin_id, $options );
* Set the queue for the plugin if required.
* We should always schedule all notice types even if they
* are disabled. Then only we can enable it later easily.
* Disabled notices won't be considered when taken from the queue.
* @param array $options Options.
* @param string $plugin_id Plugin ID.
protected function add_to_queue( $plugin_id, array $options ) {
// Store to notice queue if not saved already.
if ( ! isset( $this->stored['plugins'][ $plugin_id ] ) ) {
$this->stored['plugins'][ $plugin_id ] = time();
$this->stored['queue'][ $plugin_id ] = array();
foreach ( $this->get_types() as $type => $class_name ) {
$notice = $this->get_notice( $plugin_id, $type );
if ( ! empty( $notice ) ) {
$this->stored['queue'][ $plugin_id ][ $type ] = $notice->get_next_schedule( $options['installed_on'] );
if ( ! empty( $options['basename'] ) ) {
$this->maybe_upgrade( $plugin_id, $options['basename'] );
// Update the stored data.
* Init the notices stored data.
* Get from the db only if not already initialized.
protected function init_option() {
if ( null === $this->stored ) {
$queue = (array) get_site_option( $this->option_name, array() );
$this->stored = wp_parse_args(