: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
use AdvancedAds\Entities;
use AdvancedAds\Installation\Capabilities;
use AdvancedAds\Utilities\WordPress;
* WordPress integration and definitions:
class Advanced_Ads_Plugin {
* Instance of Advanced_Ads_Plugin
* @var object Advanced_Ads_Plugin
protected static $instance;
* Instance of Advanced_Ads_Model
* @var object Advanced_Ads_Model
* Interal plugin options – set by the plugin
* @var array $internal_options
protected $internal_options;
* Default prefix of selectors (id, class) in the frontend
* can be changed by options
* @var Advanced_Ads_Plugin
const DEFAULT_FRONTEND_PREFIX = 'advads-';
* Frontend prefix for classes and IDs
* @var string $frontend_prefix
private $frontend_prefix;
* Advanced_Ads_Plugin constructor.
private function __construct() {
add_action( 'plugins_loaded', [ $this, 'wp_plugins_loaded' ], 20 );
add_action( 'init', [ $this, 'run_upgrades' ], 9 );
* Get instance of Advanced_Ads_Plugin
* @return Advanced_Ads_Plugin
public static function get_instance() {
// If the single instance hasn't been set, set it now.
if ( null === self::$instance ) {
self::$instance = new self();
* Get instance of Advanced_Ads_Model
* @param Advanced_Ads_Model $model model to access data.
public function set_model( Advanced_Ads_Model $model ) {
* Execute various hooks after WordPress and all plugins are available
public function wp_plugins_loaded() {
// Load plugin text domain.
$this->load_plugin_textdomain();
// Load public-facing style sheet and JavaScript.
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'wp_head', [ $this, 'print_head_scripts' ], 7 );
// higher priority to make sure other scripts are printed before.
add_action( 'wp_footer', [ $this, 'print_footer_scripts' ], 100 );
add_shortcode( 'the_ad', [ $this, 'shortcode_display_ad' ] );
add_shortcode( 'the_ad_group', [ $this, 'shortcode_display_ad_group' ] );
add_shortcode( 'the_ad_placement', [ $this, 'shortcode_display_ad_placement' ] );
add_action( 'widgets_init', [ $this, 'widget_init' ] );
// Call action hooks for ad status changes.
add_action( 'transition_post_status', [ $this, 'transition_ad_status' ], 10, 3 );
// register expired post status.
Advanced_Ads_Ad_Expiration::register_post_status();
// if expired ad gets untrashed, revert it to expired status (instead of draft).
add_filter( 'wp_untrash_post_status', [ Advanced_Ads_Ad_Expiration::class, 'wp_untrash_post_status' ], 10, 3 );
// load display conditions.
Advanced_Ads_Display_Conditions::get_instance();
new Advanced_Ads_Frontend_Checks();
new Advanced_Ads_Compatibility();
Advanced_Ads_Ad_Health_Notices::get_instance(); // load to fetch notices.
* Compatibility with the Piklist plugin that has a function hooked to `posts_where` that access $GLOBALS['wp_query'].
* Since `Advanced_Ads_Upgrades` applies `posts_where`: (`Advanced_Ads_Admin_Notices::get_instance()` >
* `Advanced_Ads::get_number_of_ads()` > new WP_Query > ... 'posts_where') this function is hooked to `init` so that `$GLOBALS['wp_query']` is instantiated.
public function run_upgrades() {
* Run upgrades, if this is a new version or version does not exist.
$internal_options = $this->internal_options();
if ( ! defined( 'DOING_AJAX' ) && ( ! isset( $internal_options['version'] ) || version_compare( $internal_options['version'], ADVADS_VERSION, '<' ) ) ) {
new Advanced_Ads_Upgrades();
* Return the plugin slug.
* @return string plugin slug variable.
public function get_plugin_slug() {
* Register and enqueues public-facing JavaScript files.
public function enqueue_scripts() {
$this->get_plugin_slug() . '-advanced-js',
sprintf( '%spublic/assets/js/advanced%s.js', ADVADS_BASE_URL, defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' ),
$privacy = Advanced_Ads_Privacy::get_instance();
$privacy_options = $privacy->options();
$privacy_options['enabled'] = ! empty( $privacy_options['enabled'] );
$privacy_options['state'] = $privacy->get_state();
$this->get_plugin_slug() . '-advanced-js',
'blog_id' => get_current_blog_id(),
'privacy' => $privacy_options,
$activated_js = apply_filters( 'advanced-ads-activate-advanced-js', isset( $this->options()['advanced-js'] ) );
if ( $activated_js || ! empty( $_COOKIE['advads_frontend_picker'] ) ) {
wp_enqueue_script( $this->get_plugin_slug() . '-advanced-js' );
$this->get_plugin_slug() . '-frontend-picker',
sprintf( '%spublic/assets/js/frontend-picker%s.js', ADVADS_BASE_URL, defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' ),
[ 'jquery', $this->get_plugin_slug() . '-advanced-js' ],
if ( ! empty( $_COOKIE['advads_frontend_picker'] ) ) {
wp_enqueue_script( $this->get_plugin_slug() . '-frontend-picker' );
* Print public-facing JavaScript in the HTML head.
public function print_head_scripts() {
$short_url = self::get_short_url();
$attribution = '<!-- ' . $short_url . ' is managing ads with Advanced Ads%1$s%2$s -->';
$version = self::is_new_user( 1585224000 ) ? ' ' . ADVADS_VERSION : '';
$plugin_url = self::get_group_by_url( $short_url, 'a' ) ? ' – https://wpadvancedads.com/' : '';
// escaping would break HTML comment tags so we disable checks here.
echo apply_filters( 'advanced-ads-attribution', sprintf( $attribution, $version, $plugin_url ) );
$frontend_prefix = $this->get_frontend_prefix();
<script id="<?php echo esc_attr( $frontend_prefix ); ?>ready">
'%spublic/assets/js/ready%s.js',
defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'
* Print inline script in the page header form add-ons.
* @param string $frontend_prefix the prefix used for Advanced Ads related HTML ID-s and classes.
do_action( 'advanced_ads_inline_header_scripts', $frontend_prefix );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaping would break the HTML
echo Advanced_Ads_Utils::get_inline_asset( ob_get_clean() );
* Print inline scripts in wp_footer.
public function print_footer_scripts() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaping would break the HTML
echo Advanced_Ads_Utils::get_inline_asset(
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- we're getting the contents of a local file
sprintf( '<script>%s</script>', file_get_contents( sprintf(
'%spublic/assets/js/ready-queue%s.js',
defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'
* Register the Advanced Ads widget
public function widget_init() {
register_widget( 'Advanced_Ads_Widget' );
* Load the plugin text domain for translation.
public function load_plugin_textdomain() {
load_plugin_textdomain( 'advanced-ads', false, ADVADS_BASE_DIR . '/languages' );
* Shortcode to include ad in frontend
* @param array $atts shortcode attributes.
* @return string ad content.
public function shortcode_display_ad( $atts ) {
$atts = is_array( $atts ) ? $atts : [];
$id = isset( $atts['id'] ) ? (int) $atts['id'] : 0;
// check if there is an inline attribute with or without value.
if ( isset( $atts['inline'] ) || in_array( 'inline', $atts, true ) ) {
$atts['inline_wrapper_element'] = true;
$atts = $this->prepare_shortcode_atts( $atts );
// use the public available function here.
return get_ad( $id, $atts );
* Shortcode to include ad from an ad group in frontend
* @param array $atts shortcode attributes.
* @return string ad group content.
public function shortcode_display_ad_group( $atts ) {
$atts = is_array( $atts ) ? $atts : [];
$id = isset( $atts['id'] ) ? (int) $atts['id'] : 0;
$atts = $this->prepare_shortcode_atts( $atts );
// use the public available function here.
return get_ad_group( $id, $atts );
* Shortcode to display content of an ad placement in frontend
* @param array $atts shortcode attributes.
* @return string ad placement content.
public function shortcode_display_ad_placement( $atts ) {
$atts = is_array( $atts ) ? $atts : [];
$id = isset( $atts['id'] ) ? (string) $atts['id'] : '';
$atts = $this->prepare_shortcode_atts( $atts );
// use the public available function here.
return get_ad_placement( $id, $atts );
* Prepare shortcode attributes.
* @param array $atts array with strings.
private function prepare_shortcode_atts( $atts ) {
* Prepare attributes by converting strings to multi-dimensional array
* Example: [ 'output__margin__top' => 1 ] => ['output']['margin']['top'] = 1
if ( ! defined( 'ADVANCED_ADS_DISABLE_CHANGE' ) || ! ADVANCED_ADS_DISABLE_CHANGE ) {
foreach ( $atts as $attr => $data ) {
$levels = explode( '__', $attr );
$last = array_pop( $levels );
foreach ( $levels as $lvl ) {
if ( ! isset( $cur_lvl[ $lvl ] ) ) {
$cur_lvl = &$cur_lvl[ $lvl ];
$cur_lvl[ $last ] = $data;
$result = array_diff_key(
// Ad type: 'content' and a shortcode inside.
if ( isset( $atts['ad_args'] ) ) {
$result = array_merge( $result, json_decode( urldecode( $atts['ad_args'] ), true ) );
* these are the options updated by the user
public function options() {
// we can’t store options if WPML String Translations is enabled, or it would not translate the "Ad Label" option.
if ( ! isset( $this->options ) || class_exists( 'WPML_ST_String' ) ) {
$this->options = get_option( ADVADS_SLUG, [] );
// allow to change options dynamically
$this->options = apply_filters( 'advanced-ads-options', $this->options );
* Update plugin options (not for settings page, but if automatic options are needed)
* @param array $options new options.
public function update_options( array $options ) {
// do not allow to clear options.
$this->options = $options;
update_option( ADVADS_SLUG, $options );
* Return internal plugin options
* these are options set by the plugin
public function internal_options() {
if ( ! isset( $this->internal_options ) ) {
'version' => ADVADS_VERSION,
'installed' => time(), // when was this installed.
$this->internal_options = get_option( ADVADS_SLUG . '-internal', [] );
if ( [] === $this->internal_options ) {
$this->internal_options = $defaults;
$this->update_internal_options( $this->internal_options );
self::get_instance()->create_capabilities();
// for versions installed prior to 1.5.3 set installed date for now.
if ( ! isset( $this->internal_options['installed'] ) ) {
$this->internal_options['installed'] = time();
$this->update_internal_options( $this->internal_options );
return $this->internal_options;
* Update internal plugin options
* @param array $options new internal options.
public function update_internal_options( array $options ) {
// do not allow to clear options.
$this->internal_options = $options;
update_option( ADVADS_SLUG . '-internal', $options );
* Get prefix used for frontend elements
public function get_frontend_prefix() {
if ( isset( $this->frontend_prefix ) ) {
return $this->frontend_prefix;
$options = $this->options();
if ( ! isset( $options['front-prefix'] ) ) {
if ( isset( $options['id-prefix'] ) ) {
// deprecated: keeps widgets working that previously received an id based on the front-prefix.
$frontend_prefix = $options['id-prefix'];
$frontend_prefix = preg_match( '/[A-Za-z][A-Za-z0-9_]{4}/', parse_url( get_home_url(), PHP_URL_HOST ), $result )
: self::DEFAULT_FRONTEND_PREFIX;
$frontend_prefix = $options['front-prefix'];
* Applying the filter here makes sure that it is the same frontend prefix for all
* calls on this page impression
* @param string $frontend_prefix
$this->frontend_prefix = (string) apply_filters( 'advanced-ads-frontend-prefix', $frontend_prefix );
$this->frontend_prefix = $this->sanitize_frontend_prefix( $frontend_prefix );
return $this->frontend_prefix;
* Sanitize the frontend prefix to result in valid HTML classes.
* See https://www.w3.org/TR/selectors-3/#grammar for valid tokens.
* @param string $prefix The HTML class to sanitize.
* @param string $fallback The fallback if the class is invalid.