: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
<?php // phpcs:ignoreFile
use AdvancedAds\Utilities\Conditional;
* Container class for Ad Health notice handling
* @subpackage Advanced Ads Plugin
* related scripts / functions
* advads_push_notice() function to push notifications using AJAX in admin/assets/js/admin-global.js
* push_ad_health_notice() in Advanced_Ads_Ad_Ajax_Callbacks to push notifications sent via AJAX
* Advanced_Ads_Checks – for the various checks
* list of notification texts in admin/includes/ad-health-notices.php
class Advanced_Ads_Ad_Health_Notices {
* Instance of this class.
protected static $instance = null;
* 'text' - if not given, it uses the default text for output )
* 'orig_key' - original notice key
* All displayed notices ($notices minus $hidden)
public $displayed_notices = [];
public $default_notices = [];
* The last notice key saved
public $last_saved_notice_key = false;
* Name of the transient saved for daily checks in the backend
const DAILY_CHECK_TRANSIENT_NAME = 'advanced-ads-daily-ad-health-check-ran';
* Advanced_Ads_Ad_Health_Notices constructor.
public function __construct() {
// failsafe for there were some reports of 502 errors.
if ( 1 < did_action( 'plugins_loaded' ) ) {
// stop here if notices are disabled.
if ( ! self::notices_enabled() ) {
if ( [] === $this->default_notices ) {
include ADVADS_ABSPATH . '/admin/includes/ad-health-notices.php';
$this->default_notices = $advanced_ads_ad_health_notices;
// fills the class arrays.
* needs to run after plugins_loaded with priority 10
* current_screen seems like the perfect hook
add_action( 'current_screen', [ $this, 'run_checks' ], 20 );
// add notification when an ad expires.
add_action( 'advanced-ads-ad-expired', [ $this, 'ad_expired' ], 10, 2 );
* Check if notices are enabled using "disable-notices" option in plugin settings
public static function notices_enabled() {
$options = Advanced_Ads::get_instance()->options();
return empty( $options['disable-notices'] );
* Return an instance of this class.
* @return object A single instance of this class.
public static function get_instance() {
// If the single instance hasn't been set, set it now.
if ( null === self::$instance ) {
self::$instance = new self();
public function load_notices() {
$options = $this->options();
// load notices from "notices".
$this->notices = isset( $options['notices'] ) ? $options['notices'] : [];
foreach ( $this->notices as $_key => $_notice ) {
// without valid key caused by an issue prior to 1.13.3.
unset( $this->notices[ $_key ] );
$time = current_time( 'timestamp', 0 );
$notice_array = $this->get_notice_array_for_key( $_key );
// handle notices with a timeout.
if ( isset( $_notice['closed'] ) ) {
// remove notice when timeout expired – was closed longer ago than timeout set in the notice options.
if ( empty( $notice_array['timeout'] )
|| ( ( $time - $_notice['closed'] ) > $notice_array['timeout'] ) ) {
// just ignore notice if timeout is still valid.
unset( $this->notices[ $_key ] );
// check if notice still exists.
if ( [] === $this->get_notice_array_for_key( $_key ) ) {
unset( $this->notices[ $_key ] );
// unignore notices if `show-hidden=true` is set in the URL.
if ( isset( $_GET['advads_nonce'] ) && wp_verify_nonce( wp_unslash( $_GET['advads_nonce'] ), 'advanced-ads-show-hidden-notices' )
&& isset( $_GET['advads-show-hidden-notices'] ) && 'true' === $_GET['advads-show-hidden-notices'] ) {
// remove the argument from the URL.
add_filter( 'removable_query_args', [ $this, 'remove_query_vars_after_notice_update' ] );
$this->ignore = $this->get_valid_ignored();
$notice_keys = array_keys( $this->notices );
$this->displayed_notices = array_diff( $notice_keys, $this->ignore );
* Remove query var from URL after notice was updated
* @param array $removable_query_args array with removable query vars.
* @return array updated query vars.
public function remove_query_vars_after_notice_update( $removable_query_args ) {
$removable_query_args[] = 'advads-show-hidden-notices';
$removable_query_args[] = 'advads_nonce';
return $removable_query_args;
* Manage when to run checks
* - only when users have ads
* - once per day on any backend page
* - on each Advanced Ads related page
public function run_checks() {
// run in WP Admin only and if there are any ads.
if ( ! is_admin() || ! Advanced_Ads::get_number_of_ads() ) {
// don’t run on AJAX calls.
// run only daily unless we are on an Advanced Ads related page.
if ( ! Conditional::is_screen_advanced_ads()
&& get_transient( self::DAILY_CHECK_TRANSIENT_NAME ) ) {
* General checks done on each Advanced Ads-related page or once per day
public function checks() {
if ( ! Advanced_Ads_Checks::php_version_minimum() ) {
$this->remove( 'old_php' );
if ( count( Advanced_Ads_Checks::conflicting_plugins() ) ) {
$this->add( 'conflicting_plugins' );
$this->remove( 'conflicting_plugins' );
if ( count( Advanced_Ads_Checks::php_extensions() ) ) {
$this->add( 'php_extensions_missing' );
$this->remove( 'php_extensions_missing' );
if ( Advanced_Ads_Checks::ads_disabled() ) {
$this->add( 'ads_disabled' );
$this->remove( 'ads_disabled' );
if ( Advanced_Ads_Checks::get_defined_constants() ) {
$this->add( 'constants_enabled' );
$this->remove( 'constants_enabled' );
if ( Advanced_Ads_Checks::assets_expired() ) {
$this->add( 'assets_expired' );
$this->remove( 'assets_expired' );
if ( Advanced_Ads_Checks::licenses_invalid() ) {
$this->add( 'license_invalid' );
$this->remove( 'license_invalid' );
if ( class_exists( 'BuddyPress', false ) && ! defined( 'AAP_VERSION' ) ) {
$this->add( 'buddypress_no_pro' );
$this->remove( 'buddypress_no_pro' );
if ( class_exists( 'bbPress', false ) && ! defined( 'AAP_VERSION' ) ) {
$this->add( 'bbpress_no_pro' );
$this->remove( 'bbpress_no_pro' );
if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
$this->add( 'WPML_active' );
$this->remove( 'WPML_active' );
if ( Advanced_Ads_Checks::active_amp_plugin() ) {
$this->add( 'AMP_active' );
$this->remove( 'AMP_active' );
if ( Advanced_Ads_Checks::wp_engine_hosting() ) {
$this->add( 'wpengine' );
if ( count( Advanced_Ads_Checks::ads_txt_plugins() ) ) {
$this->add( 'ads_txt_plugins_enabled' );
$this->remove( 'ads_txt_plugins_enabled' );
if ( count( Advanced_Ads_Checks::header_footer_plugins() ) ) {
$this->add( 'header_footer_plugins_enabled' );
$this->remove( 'header_footer_plugins_enabled' );
set_transient( self::DAILY_CHECK_TRANSIENT_NAME, true, DAY_IN_SECONDS );
* Add a notice to the queue
* @param string $notice_key notice key to be added to the notice array.
* @param array $atts additional attributes.
* - append_key string attached to the key; enables to create multiple messages for one original key
* - append_text text added to the default message
* - ad_id ID of an ad, attaches the link to the ad edit page to the message
public function add( $notice_key, $atts = [] ) {
// stop here if notices are disabled.
if ( empty( $notice_key ) || ! self::notices_enabled() ) {
if ( ! empty( $atts['append_key'] ) ) {
$orig_notice_key = $notice_key;
$notice_key .= $atts['append_key'];
$notice_key = esc_attr( $notice_key );
$options_before = $options = $this->options();
// load notices from "queue".
$notices = isset( $options['notices'] ) ? $options['notices'] : [];
// check if notice_key was already saved, this prevents the same notice from showing up in different forms.
if ( isset( $notices[ $notice_key ] ) ) {
// save the new notice key.
$notices[ $notice_key ] = [];
if ( ! empty( $atts['text'] ) ) {
$notices[ $notice_key ]['text'] = $atts['text'];
// attach link to ad, if given.
if ( ! empty( $atts['ad_id'] ) ) {
$id = absint( $atts['ad_id'] );
$ad = \Advanced_Ads\Ad_Repository::get( $id );
if ( $id && isset( $ad->title ) && '' !== $ad->title ) {
$edit_link = ' <a href="' . get_edit_post_link( $id ) . '">' . $ad->title . '</a>';
$notices[ $notice_key ]['append_text'] = isset( $notices[ $notice_key ]['append_text'] ) ? $notices[ $notice_key ]['append_text'] . $edit_link : $edit_link;
// save the original key, if we manipulated it.
if ( ! empty( $atts['append_key'] ) ) {
$notices[ $notice_key ]['orig_key'] = $orig_notice_key;
if ( ! empty( $atts['append_text'] ) ) {
$notices[ $notice_key ]['append_text'] = esc_attr( $atts['append_text'] );
// add current time – we store localized time including the offset set in WP.
$notices[ $notice_key ]['time'] = current_time( 'timestamp', 0 );
$this->last_saved_notice_key = $notice_key;
$options['notices'] = $notices;
if ( $options_before !== $options ) {
$this->update_options( $options );
// update already registered notices.
* Updating an existing notice or add it, if it doesn’t exist, yet
* @param string $notice_key notice key to be added to the notice array.
* @param array $atts additional attributes.
* - append_text – text added to the default message
public function update( $notice_key, $atts = [] ) {
// stop here if notices are disabled.
if ( empty( $notice_key ) || ! self::notices_enabled() ) {
// check if the notice already exists.
$notice_key = esc_attr( $notice_key );
$options = $this->options();
$options_before = $options;
// load notices from "queue".
$notices = isset( $options['notices'] ) ? $options['notices'] : [];
// check if notice_key was already saved, this prevents the same notice from showing up in different forms.
if ( ! isset( $notices[ $notice_key ] ) ) {
$this->add( $notice_key, $atts );
$notice_key = $this->last_saved_notice_key;
// just in case, get notices again.
$notices = $this->notices;
// add more text if this is an update.
if ( ! empty( $atts['append_text'] ) ) {
$notices[ $notice_key ]['append_text'] = isset( $notices[ $notice_key ]['append_text'] ) ? $notices[ $notice_key ]['append_text'] . $atts['append_text'] : $atts['append_text'];
// add `closed` marker, if given.
if ( ! empty( $atts['closed'] ) ) {
$notices[ $notice_key ]['closed'] = absint( $atts['closed'] );
$options['notices'] = $notices;
if ( $options_before !== $options ) {
$this->update_options( $options );
// update already registered notices.
* Decide based on the notice, whether to remove or ignore it
* @param string $notice_key key of the notice.
public function hide( $notice_key ) {
if ( empty( $notice_key ) ) {
// get original notice array for the "hide" attribute.
$notice_array = $this->get_notice_array_for_key( $notice_key );
// handle notices with a timeout.
// set `closed` timestamp if the notice definition has a timeout information.
if ( isset( $notice_array['timeout'] ) ) {
$this->update( $notice_key, [ 'closed' => current_time( 'timestamp', 0 ) ] );
if ( isset( $notice_array['hide'] ) && false === $notice_array['hide'] ) {
self::get_instance()->remove( $notice_key );
self::get_instance()->ignore( $notice_key );
* Would remove it from "notice" array. The notice can be added anytime again
* practically, this allows users to "skip" an notice if they are sure that it was only temporary
* @param string $notice_key notice key to be removed.
public function remove( $notice_key ) {
// stop here if notices are disabled.
if ( empty( $notice_key ) || ! self::notices_enabled() ) {