Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Utilities\WordPress;
/**
* Allow serving ads on external URLs.
*
* Class Advanced_Ads_Pro_Module_Ad_Server_Admin
*/
class Advanced_Ads_Pro_Module_Ad_Server_Admin {
/**
* Advanced_Ads_Pro_Module_Ad_Server_Admin constructor.
*/
public function __construct() {
// Add settings section to allow module enabling.
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ] );
// Check if the module was enabled.
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( empty( $options['ad-server']['enabled'] ) ) {
return;
}
// Show usage information under "show all options".
add_filter( 'advanced-ads-placement-options-after-advanced', [ $this, 'add_placement_setting' ], 10, 2 );
}
/**
* Option to enable the Ad Server module.
*/
public function settings_init() {
// Add new section.
add_settings_field(
'module-ad-server',
__( 'Ad Server', 'advanced-ads-pro' ),
[ $this, 'render_settings' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings',
Advanced_Ads_Pro::OPTION_KEY . '_modules-enable'
);
}
/**
* Render Ad Server module option.
*/
public function render_settings() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
$module_enabled = isset( $options['ad-server']['enabled'] ) && $options['ad-server']['enabled'];
$embedding_url = isset( $options['ad-server']['embedding-url'] ) ? $options['ad-server']['embedding-url'] : '';
$block_no_referrer = ! empty( $options['ad-server']['block-no-referrer'] ); // True if option is set.
include dirname( __FILE__ ) . '/views/module-settings.php';
}
/**
* Show usage information for the ad server
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function add_placement_setting( $placement_slug, $placement ) {
if ( ! $placement->is_type( 'server' ) ) {
return;
}
// Publically visible name of the placement. Defaults to the placement slug.
$placement_options = $placement->get_data();
$public_slug = ! empty( $placement_options['ad-server-slug'] ) ? sanitize_title( $placement_options['ad-server-slug'] ) : $placement_slug;
ob_start();
include dirname( __FILE__ ) . '/views/placement-settings.php';
$slug_content = ob_get_clean();
WordPress::render_option(
'ad-server-usage',
__( 'Public string', 'advanced-ads-pro' ),
$slug_content
);
$options = Advanced_Ads_Pro::get_instance()->get_options();
// Static URL used for the placement to deliver the content.
$url = admin_url( 'admin-ajax.php' ) . '?action=aa-server-select&p=' . $public_slug;
ob_start();
include dirname( __FILE__ ) . '/views/placement-usage.php';
$usage_content = ob_get_clean();
WordPress::render_option(
'ad-server-usage',
__( 'Usage', 'advanced-ads-pro' ),
$usage_content
);
}
}

View File

@@ -0,0 +1,4 @@
<?php
new Advanced_Ads_Pro_Module_Ad_Server_Admin();

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,195 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Framework\Utilities\Params;
/**
* Allow serving ads on external URLs.
*/
class Advanced_Ads_Pro_Module_Ad_Server {
/**
* Advanced_Ads_Pro_Module_Ad_Server constructor.
*/
public function __construct() {
// Register frontend AJAX calls.
add_action( 'wp_ajax_aa-server-select', [ $this, 'get_placement' ] );
add_action( 'wp_ajax_nopriv_aa-server-select', [ $this, 'get_placement' ] );
add_filter( 'advanced-ads-set-wrapper', [ $this, 'ad_wrapper' ], 10, 2 );
// Add allowed HTTP origins.
if ( wp_doing_ajax() ) {
add_filter( 'allowed_http_origins', [ $this, 'add_allowed_origins' ] );
}
}
/**
* Add a wrapper to served top level ads
*
* @param array $wrapper existing wrapper data.
* @param Ad $ad the ad.
*
* @return array
*/
public function ad_wrapper( $wrapper, $ad ) {
$placement = $ad->get_root_placement();
if ( ! $placement || ! $placement->is_type( 'server' ) ) {
return $wrapper;
}
if ( ! $ad->is_top_level() ) {
return $wrapper;
}
if ( ! is_array( $wrapper ) || ! isset( $wrapper['id'] ) ) {
$wrapper['id'] = $ad->create_wrapper_id();
}
return $wrapper;
}
/**
* Load placement content
*
* Based on Advanced_Ads_Ajax::advads_ajax_ad_select()
*/
public function get_placement() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
$block_no_referrer = ! empty( $options['ad-server']['block-no-referrer'] ); // True if option is set.
// Prevent direct access through the URL.
if ( $block_no_referrer && ! Params::server( 'HTTP_REFERER' ) ) {
die( 'direct access forbidden' );
}
// Set correct frontend headers.
header( 'X-Robots-Tag: noindex,nofollow' );
header( 'Content-Type: text/html; charset=UTF-8' );
$embedding_urls = $this->get_embedding_urls();
// Cross Origin Resource Sharing.
if ( ! empty( $embedding_urls ) ) {
$embedding_urls_string = implode( ' ', $embedding_urls );
header( 'Content-Security-Policy: frame-ancestors ' . $embedding_urls_string );
foreach ( $embedding_urls as $url ) {
$parsed_url = wp_parse_url( $url );
$scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : 'https://';
header( 'Access-Control-Allow-Origin: ' . $scheme . $parsed_url['host'] );
}
}
$public_slug = Params::request( 'p', null );
if ( empty( $public_slug || ! is_string( $public_slug ) ) ) {
die( 'missing p parameter' );
}
// Get placement output by public slug.
$placement_content = $this->get_placement_output_by_public_slug( $public_slug );
include __DIR__ . '/views/frontend-template.php';
die();
}
/**
* Modify the ad object before serving
*
* @param false|string $override overridden ad output.
* @param Ad $ad the ad.
*
* @return false
*/
public function override_ad_object( $override, $ad ) {
/**
* We need to force the ad to open in a new window when the link is created through Advanced Ads. Otherwise,
* clicking the ad in an iframe would load the target page in the iframe, too.
*
* 1. The Tracking add-on has a dedicated option on the ad edit page for this.
* We are setting it to open in a new window here and ignore the options the user might have set.
*/
$ad->set_prop_temp( 'tracking.target', 'new' );
// Ignore consent settings for ad-server ads.
$ad->set_prop_temp( 'privacy.ignore-consent', 'on' );
/**
* 2. The Advanced Ads plugin adds target="_blank" based on a global option
* We change force that option to open ads in a new window by hooking into the advanced-ads-options filter below.
*/
add_filter(
'advanced-ads-options',
function ( $options ) {
$options['target-blank'] = 1;
return $options;
}
);
return false;
}
/**
* Get the content of a placement based on the public slug.
*
* @param string $public_slug placement ID or public slug.
*/
private function get_placement_output_by_public_slug( $public_slug = '' ) {
if ( '' === $public_slug ) {
return '';
}
$placement = wp_advads_get_placement( $public_slug );
// Return placement if there is one with public_slug being the placement ID.
if ( $placement ) {
add_filter( 'advanced-ads-ad-select-override-by-ad', [ $this, 'override_ad_object' ], 10, 2 );
return $placement->output();
}
// Load all placements.
$placements = wp_advads_get_placements();
// Iterate through "ad-server" placements and look for the one with the public slug.
foreach ( $placements as $placement ) {
if ( $placement->is_type( 'server' ) && $public_slug === $placement->get_prop( 'ad-server-slug' ) ) {
add_filter( 'advanced-ads-ad-select-override-by-ad', [ $this, 'override_ad_object' ], 10, 3 );
return $placement->output();
}
}
}
/**
* Add allowed HTTP origins.
* Needed for the JavaScript-based implementation of the placement.
*
* @param array $origins Allowed HTTP origins.
* @return array $origins Allowed HTTP origins.
*/
public function add_allowed_origins( $origins ) {
$embedding_urls = $this->get_embedding_urls();
if ( is_array( $embedding_urls ) && count( $embedding_urls ) ) {
$origins = array_merge( $origins, $embedding_urls );
}
return $origins;
}
/**
* Get the embedding URL array
*
* @return array $embedding_urls.
*/
public function get_embedding_urls() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
$embedding_url_option = isset( $options['ad-server']['embedding-url'] ) ? $options['ad-server']['embedding-url'] : false;
$embedding_urls_raw = explode( ',', $embedding_url_option );
$embedding_urls = [];
foreach ( $embedding_urls_raw as $_url ) {
$embedding_urls[] = esc_url_raw( $_url );
}
return $embedding_urls;
}
}

View File

@@ -0,0 +1,4 @@
<?php
new Advanced_Ads_Pro_Module_Ad_Server;

View File

@@ -0,0 +1,26 @@
<?php
/**
* Template to show the ad.
*
* @package Advanced_Ads_Pro
*
* @var string $placement_content content of the placement.
* @var string $public_slug public slug or placement ID send as $_GET['p'] to allow placement-specific usage of the hooks.
*/
?>
<!DOCTYPE html>
<html>
<head>
<meta name="robots" content="noindex,nofollow">
<?php do_action( 'advanced-ads-pro-ad-server-template-head', $public_slug ); ?>
</head>
<body style="margin: 0;" >
<?php do_action( 'advanced-ads-pro-ad-server-template-after-opening-body', $public_slug ); ?>
<?php
// phpcs:ignore
echo $placement_content;
?>
<?php do_action( 'advanced-ads-pro-ad-server-template-before-closing-body', $public_slug ); ?>
</body>
</html>

View File

@@ -0,0 +1,57 @@
<?php
/**
* Render options for the Ad Server module
*
* @var string $embedding_url URL where the ad should be loaded.
* @var boolean $block_no_referrer Value of the block-no-referrer option.
*/
?>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[ad-server][enabled]"
class="advads-has-sub-settings"
id="advanced-ads-pro-ad-server-enabled" type="checkbox" value="1" <?php checked( $module_enabled ); ?> />
<label for="advanced-ads-pro-ad-server-enabled" class="description">
<?php esc_html_e( 'Activate module.', 'advanced-ads-pro' ); ?>
</label>
<a href="https://wpadvancedads.com/ad-server-wordpress/?utm_source=advanced-ads&utm_medium=link&utm_campaign=pro-ad-server-manual'; ?>" target="_blank" class="advads-manual-link"><?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?></a>
<div class="advads-sub-settings">
<p class="description"><?php esc_html_e( 'Top level domains on which the ads will be loaded.', 'advanced-ads-pro' ); ?> <?php esc_html_e( 'Separate multiple values with a comma.', 'advanced-ads-pro' ); ?></p>
<label>
<input style="width: 90%" id="advanced-ads-pro-server-domains"
name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[ad-server][embedding-url]" type="text"
value="<?php echo esc_html( $embedding_url ); ?>"/>
<p id="advanced-ads-pro-server-domains-error"
class="advads-notice-inline advads-error hidden"><?php esc_html_e( 'Please dont enter subdirectories.', 'advanced-ads-pro' ); ?></p>
</label>
<br/><br/>
<label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[ad-server][block-no-referrer]"
type="checkbox" value="1" <?php checked( $block_no_referrer ); ?> />
<?php esc_html_e( 'Prevent direct access to the placement URL.', 'advanced-ads-pro' ); ?>
</label>
</div>
<script>
// check if input is valid URLs without subdirectories
jQuery(document).ready(function () {
jQuery( '#advanced-ads-pro-server-domains' ).on( 'change', function () {
// Sudirectories are not allowed so lets just check for the / character
advanced_ads_pro_server_check_target_urls( jQuery(this).val() );
});
});
// run the check once on load.
advanced_ads_pro_server_check_target_urls( jQuery( '#advanced-ads-pro-server-domains' ).val() );
/**
* check if the URLs of the target sites are valid
* if not, show a warning
*
* @param string value of the target URL.
*/
function advanced_ads_pro_server_check_target_urls( value ) {
// is there a "/" with a preceding and following alphanumeric value then this might be a subdirectory
if ( /[a-z0-9]\/[a-z0-9]/.test( value ) ) {
jQuery('#advanced-ads-pro-server-domains-error').show();
} else {
jQuery('#advanced-ads-pro-server-domains-error').hide();
}
}
</script>

View File

@@ -0,0 +1,18 @@
<?php //phpcs:ignoreFile
/**
* Show placement related options
*
* @var string $public_slug URL where the ad placement can be accessed directly.
*/
?>
<input type="text" id="advanced-ads-pro-placement-server-slug" name="advads[placements][options][ad-server-slug]" value="<?php echo esc_attr( $public_slug ); ?>" />
<p id="advanced-ads-pro-placement-server-slug-update-message" class="advads-notice-inline advads-error hidden"><?php esc_html_e( 'Save the page to update the usage code below.', 'advanced-ads-pro' ); ?></p>
<p class="description"><?php esc_html_e( 'The name of the placement that appears in the URL and injection code.', 'advanced-ads-pro' ); ?></p>
<script>
jQuery( document ).ready( function() {
jQuery( '#advanced-ads-pro-placement-server-slug' ).on( 'change', function(){
jQuery( '#advanced-ads-pro-placement-server-slug-update-message' ).show();
});
});
</script>

View File

@@ -0,0 +1,32 @@
<?php
/**
* Show examples on how to use the ad server placement.
*
* @package AdvancedAds\Pro
*
* @var string $url URL where the ad placement can be accessed directly.
* @var string $placement_slug placement ID.
* @var string $public_slug public name of the placement.
*/
?>
<label>
<p><?php esc_html_e( 'Direct URL', 'advanced-ads-pro' ); ?></p>
<input type="text" onclick="this.select();" readonly="readonly" value="<?php echo esc_url( $url ); ?>" style="width:600px;max-width:90%;"/>
</label>
<br/><br/>
<label>
<p>iframe</p>
<input type="text" onclick="this.select();" readonly="readonly" value="<?php echo esc_html( '<iframe src="' . $url . '" scrolling="no" width="300" height="250" style="overflow: hidden;border:none;"></iframe>' ); ?>" style="width:600px;max-width:90%;"/>
</label>
<br/><br/>
<label>
<p>JavaScript</p>
<?php //phpcs:disable ?>
<textarea onclick="this.select();" readonly="readonly" style="width:600px;max-width:90%;" rows="5">
<div id="<?php echo $public_slug; ?>-box"></div>
<script>
fetch('<?php echo esc_url( $url ); ?>').then(function(e) { return e.text();}).then(function(body) { var server_parser = new DOMParser(); var doc = server_parser.parseFromString(body, "text/html"); var ad_container = doc.querySelector('div'); document.querySelector('#<?php echo $public_slug; ?>-box').innerHTML = ad_container.innerHTML; });
</script></textarea>
<?php //phpcs:enable ?>
</label>

View File

@@ -0,0 +1,123 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin bar class
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
use AdvancedAds\Utilities\Conditional;
/**
* Admin bar functionality.
*/
class Advanced_Ads_Pro_Module_Admin_Bar {
/**
* Constructor
*/
public function __construct() {
if ( defined( 'ADVANCED_ADS_PRO_DISABLE_ADS_TOOLBAR_ITEM' ) && ADVANCED_ADS_PRO_DISABLE_ADS_TOOLBAR_ITEM ) {
return;
}
// TODO: load options
// Add admin bar item with current ads.
if ( ! is_admin() ) {
add_action( 'admin_bar_menu', [ $this, 'admin_bar_current_ads' ], 999 );
}
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ], 11 );
add_action( 'wp_footer', [ $this, 'output_items' ], 21 );
}
/**
* Add admin bar menu with current displayed ads and ad groups.
*
* @since 1.0.0
* @param WP_Admin_Bar $wp_admin_bar Admin bar class.
*/
public function admin_bar_current_ads( $wp_admin_bar ) {
// Early bail!!
if ( ! Conditional::user_can( 'advanced_ads_edit_ads' ) || ! Advanced_Ads_Ad_Health_Notices::notices_enabled() ) {
return;
}
// Add main menu item.
$args = [
'id' => 'advads_current_ads',
'title' => __( 'Ads', 'advanced-ads-pro' ),
'href' => false,
];
$wp_admin_bar->add_node( $args );
$args = [
'parent' => 'advads_current_ads',
'id' => 'advads_no_ads_found',
'title' => __( 'No Ads found', 'advanced-ads-pro' ),
'href' => false,
];
$wp_admin_bar->add_node( $args );
}
/**
* Enqueue the admin bar script.
*/
public function enqueue_scripts() {
if ( ! is_admin_bar_showing() ) {
return;
}
$uri_rel_path = AAP_BASE_URL . 'assets/js/';
$deps = [ 'jquery' ];
if ( wp_script_is( 'advanced-ads-pro/cache_busting' ) ) {
$deps[] = 'advanced-ads-pro/cache_busting';
}
wp_enqueue_script( 'advanced-ads-pro/cache_busting_admin_bar', $uri_rel_path . 'admin_bar.js', $deps, AAP_VERSION, true );
// Scrollable ads listing when ads long then windows height.
$custom_inline_style = '#wp-admin-bar-advads_current_ads-default { overflow-y: auto; max-height:calc(100vh - 50px); } ';
wp_add_inline_style( 'admin-bar', $custom_inline_style );
}
/**
* Output items that do not use cache-busting.
*/
public function output_items() {
// Add item for each ad.
$ads = \AdvancedAds\Frontend\Stats::get()->entities ?? [];
$nodes = [];
foreach ( $ads as $_ad ) {
// TODO: $type not used .
// TODO: types are extendable through Advanced_Ads_Select.
$type = '';
switch ( $_ad['type'] ) {
case 'ad':
$type = esc_html__( 'ad', 'advanced-ads-pro' );
break;
case 'group':
$type = esc_html__( 'group', 'advanced-ads-pro' );
break;
case 'placement':
$type = esc_html__( 'placement', 'advanced-ads-pro' );
break;
}
$nodes[] = [
'title' => esc_html( $_ad['title'] ),
'type' => $type,
'count' => $_ad['count'],
];
}
$content = sprintf( '<script>window.advads_admin_bar_items = %s;</script>', wp_json_encode( $nodes ) );
if ( class_exists( 'Advanced_Ads_Utils' ) && method_exists( 'Advanced_Ads_Utils', 'get_inline_asset' ) ) {
$content = Advanced_Ads_Utils::get_inline_asset( $content );
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- the output is already escaped, we can't escape it again without breaking the HTML.
echo $content;
}
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Admin_Bar;

View File

@@ -0,0 +1,81 @@
(function () {
'use strict';
// eslint-disable-next-line camelcase,no-undef
if (!window.advanced_ads_pro || !window.advads_pro_utils) {
return;
}
const date = new Date();
/**
* JS version of WP's PHP zeroise (with threshold fixed to 2)
*
* @param {string|number} num the number
* @return {string} number string with at most one leading zero
*/
const zeroise = function (num) {
num = typeof num !== 'string' ? num.toString() : num;
return num.length < 2 ? `0${num}` : num;
};
// add start and end time into passive CB ad info.
document.addEventListener('advanced-ads-passive-cb-ad-info', function (ev) {
if (typeof ev.detail.adInfo.by_hours === 'undefined') {
return;
}
const hours = ev.detail.adInfo.by_hours.split('_');
ev.detail.ad.by_hours = { start: hours[0], end: hours[1] };
});
// can display check.
document.addEventListener(
'advanced-ads-passive-cb-can-display',
function (ev) {
if (!ev.detail.adInfo.by_hours || !ev.detail.canDisplay.display) {
// no specific hours or already hidden by another check.
return;
}
const now = parseInt(
zeroise(date.getHours()) + zeroise(date.getMinutes()),
10
);
const start = parseInt(ev.detail.adInfo.by_hours.start, 10);
const end = parseInt(ev.detail.adInfo.by_hours.end, 10);
const canDisplay = {
show:
start < end
? now > start && now < end
: now > start || now < end,
};
// allow filtering of canDisplay for passive cb.
document.dispatchEvent(
new CustomEvent('advanced-ads-can-display-ads-by-hours', {
detail: {
canDisplay,
},
})
);
ev.detail.canDisplay.display = canDisplay.show;
if (!canDisplay.show) {
// eslint-disable-next-line camelcase,no-undef
advads_pro_utils.log(
'passive ad id',
ev.detail.adInfo.id,
'cannot be displayed: by_hours'
);
}
}
);
// Edit the ajax cb call payload
document.addEventListener('advanced-ads-ajax-cb-payload', function (ev) {
ev.detail.payload.browserTime = `${zeroise(date.getHours())}:${zeroise(
date.getMinutes()
)}`;
});
})();

View File

@@ -0,0 +1,13 @@
<?php
/**
* Main module class
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace Advanced_Ads_Pro\Ads_By_Hours;
const BASE_DIR = __DIR__;
const BASE_FILE = __FILE__;
Module::get_instance();

View File

@@ -0,0 +1,152 @@
<?php
/**
* Backend helper
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace Advanced_Ads_Pro\Ads_By_Hours;
use Advanced_Ads_Utils;
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Constants;
use DateTime;
/**
* Dashboard class
*/
class Admin {
/**
* Ads by hours module class
*
* @var Module
*/
private $module;
/**
* Constructor
*
* @param Module $module main module class.
*/
public function __construct( $module ) {
$this->module = $module;
if ( ! wp_doing_ajax() ) {
add_action( 'post_submitbox_misc_actions', [ $this, 'publish_metabox_markup' ], 20 );
add_filter( 'advanced-ads-ad-pre-save', [ $this, 'on_save' ], 10, 2 );
}
}
/**
* Save ad options.
*
* @param Ad $ad Ad instance.
* @param array $post_data Post data array.
*
* @return void
*/
public function on_save( $ad, $post_data ) {
$options['enabled'] = ! empty( $post_data['ads_by_hours']['enabled'] );
$posted = wp_unslash( $post_data['ads_by_hours'] ?? [] );
if ( ! isset( $options['start_hour'], $options['start_min'], $options['end_hour'], $options['end_min'] ) ) {
return;
}
$options['start_hour'] = in_array( $posted['start_hour'], $this->module->get_hours(), true ) ? sanitize_key( $posted['start_hour'] ) : '00';
$options['start_min'] = in_array( $posted['start_min'], $this->module->get_minutes(), true ) ? sanitize_key( $posted['start_min'] ) : '00';
$options['end_hour'] = in_array( $posted['end_hour'], $this->module->get_hours(), true ) ? sanitize_key( $posted['end_hour'] ) : '00';
$options['end_min'] = in_array( $posted['end_min'], $this->module->get_minutes(), true ) ? sanitize_key( $posted['end_min'] ) : '00';
$ad->set_prop( 'ads_by_hours', $options );
}
/**
* Get the warning about cache plugin
*
* @return string
*/
public function get_cache_plugin_warning() {
return __( "A cache plugin has been detected. It is recommended to enable Cache Busting and check the visitor's time when displaying the ad on the frontend.", 'advanced-ads-pro' );
}
/**
* Get the message for when CB is not detected
*
* @return string
*/
public function get_cb_warning_message() {
return __( "Showing ads depending on the visitor's time requires enabling Cache Busting.", 'advanced-ads-pro' );
}
/**
* Get localized hour to use in the publish metabox
*
* @param string $hour hour (00 to 23) to be localized.
*
* @return string
*/
public function get_localized_hour( $hour ) {
// From the saved WP tme format, replace all constants that are not hour nor timezone, preserve white spaces.
return ( new DateTime( "today $hour:00" ) )->format( preg_replace( '/[^gGhHaAeIOPTZ ]/', '', self::get_time_format() ) );
}
/**
* Get localized hour interval for the ad planning column
*
* @param Ad $ad Ad instance.
*
* @return array
*/
public function get_localized_intervals( $ad ) {
$interval = $ad->get_prop( 'ads_by_hours', [] );
if ( empty( $interval ) ) {
return [];
}
return [
'start' => ( new DateTime( "today {$interval['start_hour']}:{$interval['start_min']}" ) )->format( self::get_time_format() ),
'end' => ( new DateTime( "today {$interval['end_hour']}:{$interval['end_min']}" ) )->format( self::get_time_format() ),
];
}
/**
* Show ads by hours inputs on the publish metabox
*
* @return void
*/
public function publish_metabox_markup() {
if ( get_post_type() !== Constants::POST_TYPE_AD ) {
return;
}
$post = get_post();
$ad = wp_advads_get_ad( $post->ID );
$options = $this->module->get_ad_by_hour_options( $ad );
$addon_options = \Advanced_Ads_Pro::get_instance()->get_options();
require_once BASE_DIR . '/views/publish-metabox-inputs.php';
}
/**
* Get timezone used for the front end checks.
*
* @return string
*/
public function get_time_zone_string() {
return $this->module->use_browser_time() ? __( 'viewer time zone', 'advanced-ads-pro' ) : Advanced_Ads_Utils::get_timezone_name();
}
/**
* Get the WP time format option
*
* @return string
*/
public static function get_time_format() {
static $time_format;
if ( is_null( $time_format ) ) {
$time_format = get_option( 'time_format' );
}
return $time_format;
}
}

View File

@@ -0,0 +1,248 @@
<?php // phpcs:ignoreFile
/**
* Ads by hours main class
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace Advanced_Ads_Pro\Ads_By_Hours;
use DateTimeImmutable;
use Advanced_Ads_Utils;
use AdvancedAds\Abstracts\Ad;
/**
* Main module class
*/
class Module {
const TIME_COOKIE = 'advanced_ads_browser_time';
/**
* The singleton
*
* @var Module
*/
private static $instance;
/**
* Whether to use client side time
*
* @var bool
*/
private $use_browser_time = false;
/**
* Dashboard helper class
*
* @var Admin
*/
private $admin;
/**
* Private constructor
*/
private function __construct() {
if ( is_admin() ) {
$this->admin = new Admin( $this );
}
add_filter( 'advanced-ads-can-display-ad', [ $this, 'can_display_by_hours' ], 10, 2 );
$this->use_browser_time = defined( 'ADVANCED_ADS_ADS_BY_BROWSER_HOUR' ) && ADVANCED_ADS_ADS_BY_BROWSER_HOUR;
if ( $this->use_browser_time ) {
add_filter( 'advanced-ads-pro-ad-needs-backend-request', [ $this, 'force_cb' ], 10, 2 );
add_filter( 'advanced-ads-pro-passive-cb-for-ad', [ $this, 'add_passive_cb_info' ], 10, 2 );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
}
/**
* Adds info about hours to passive CB ad
*
* @param array $passive_cb_info information used by passive cb to decide whether the ad can be displayed.
* @param Ad $ad the ad.
*
* @return array
*/
public function add_passive_cb_info( $passive_cb_info, $ad ) {
$options = $this->get_ad_by_hour_options( $ad );
if ( empty( $options['enabled'] ) ) {
return $passive_cb_info;
}
if ( $options['start_hour'] . $options['start_min'] === $options['end_hour'] . $options['end_min'] ) {
return $passive_cb_info;
}
$passive_cb_info['by_hours'] = "{$options['start_hour']}{$options['start_min']}_{$options['end_hour']}{$options['end_min']}";
return $passive_cb_info;
}
/**
* @param string $cb_mode
* @param Ad $ad
*
* @return string
*/
public function force_cb( $cb_mode, $ad ) {
return $ad->get_prop( 'ads_by_hours.enabled' ) && 'static' === $cb_mode ? 'passive' : $cb_mode;
}
/**
* Get the admin helper
*
* @return Admin
*/
public function admin() {
return $this->admin;
}
/**
* Getter for `use_browserçtime`
*
* @return bool
*/
public function use_browser_time() {
return $this->use_browser_time;
}
/**
* Enqueue scripts on the frontend
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_script(
'AdvancedAds\Pro\ads-by-hours',
trailingslashit( plugin_dir_url( BASE_FILE ) ) . 'assets/frontend.js',
[ 'advanced-ads-pro/cache_busting' ],
AAP_VERSION,
false
);
}
/**
* Should an ad be displayed given current DateTime
*
* @param bool $can_display current value of $can_display.
* @param Ad $ad current ad.
*
* @return bool|mixed
* @throws \Exception Can't create date object.
*/
public function can_display_by_hours( $can_display, $ad ) {
if ( ! $can_display ) {
return false;
}
$module_options = $this->get_ad_by_hour_options( $ad );
if ( empty( $module_options['enabled'] ) ) {
return true;
}
if ( $module_options['start_hour'] . $module_options['start_min'] === $module_options['end_hour'] . $module_options['end_min'] ) {
return true;
}
if ( wp_doing_ajax() ) {
// AJAX CB.
// phpcs:disable WordPress.Security.NonceVerification.Missing
$post_vars = wp_unslash( $_POST );
if ( 'advads_ad_select' === sanitize_key( $post_vars['action'] ) && ! empty( $post_vars['browserTime'] ) ) {
$browser_time = explode( ':', sanitize_text_field( $post_vars['browserTime'] ) );
$now = (int) zeroise( $browser_time[0], 2 ) . zeroise( $browser_time[1], 2 );
$start = (int) $module_options['start_hour'] . $module_options['start_min'];
$end = (int) $module_options['end_hour'] . $module_options['end_min'];
// Show if $now is between $start and $end.
$can_display = $now > $start && $now < $end;
if ( $start > $end ) {
// In case of overnight, show if $now is after $start OR before $end.
$can_display = $now > $start || $now < $end;
}
return apply_filters( 'advanced-ads-can-display-ads-by-hours', $can_display, $ad, $this );
}
// phpcs:enable
}
if ( $this->use_browser_time ) {
$pro_options = \Advanced_Ads_Pro::get_instance()->get_options();
if ( ! ( isset( $pro_options['cache-busting']['enabled'] ) && $pro_options['cache-busting']['enabled'] ) ) {
// Use browser time but cache busting not enabled - abort.
return apply_filters( 'advanced-ads-can-display-ads-by-hours', false, $ad, $this );
}
if ( 'off' === $ad->get_prop( 'cache-busting' ) ) {
return apply_filters( 'advanced-ads-can-display-ads-by-hours', false, $ad, $this );
}
// otherwise let CB handle it.
return apply_filters( 'advanced-ads-can-display-ads-by-hours', true, $ad, $this );
}
$now = (int) ( new DateTimeImmutable( 'now', Advanced_Ads_Utils::get_wp_timezone() ) )->format( 'U' );
$start = (int) ( new DateTimeImmutable( "today {$module_options['start_hour']}:{$module_options['start_min']}", Advanced_Ads_Utils::get_wp_timezone() ) )->format( 'U' );
$end = (int) ( new DateTimeImmutable( "today {$module_options['end_hour']}:{$module_options['end_min']}", Advanced_Ads_Utils::get_wp_timezone() ) )->format( 'U' );
$can_display = $start < $end ? $now > $start && $now < $end : $now > $start || $now < $end;
return apply_filters( 'advanced-ads-can-display-ads-by-hours', $can_display, $ad, $this );
}
/**
* Get minutes intervals
*
* @return string[]
*/
public function get_minutes() {
return [ '00', '15', '30', '45' ];
}
/**
* Get ads by hour option for a given ad, fall back to default value if not existing
*
* @param Ad $ad current ad.
*
* @return array
*/
public function get_ad_by_hour_options( $ad ) {
return $ad->get_prop( 'ads_by_hours' ) ?? [
'enabled' => false,
'start_hour' => '00',
'start_min' => '00',
'end_hour' => '00',
'end_min' => '00',
];
}
/**
* Get list of hours, from 00 to 23
*
* @return array
*/
public function get_hours() {
$hours = [];
for ( $i = 0; $i <= 23; $i++ ) {
$hours [] = zeroise( $i, 2 );
}
return $hours;
}
/**
* Returns the singleton
*
* @return Module the singleton.
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/***
* Markup for ads by hours inputs
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*
* @var array $addon_options pro add-on options.
* @var array $options ads by hour module options.
* @var Advanced_Ads_Pro\Ads_By_Hours\admin $this dashboard class.
*/
use AdvancedAds\Utilities\Conditional;
$cache_busting_enabled = isset( $addon_options['cache-busting']['enabled'] ) && $addon_options['cache-busting']['enabled'];
?>
<div id="advanced-ads-ads-by-hours-inputs" class="misc-pub-section">
<label onclick="advads_toggle_box( '#advanced-ads-ads-by-hours-enabled', '#advanced-ads-ads-by-hours-inputs .inner' )">
<input type="checkbox" value="1" name="advanced_ad[ads_by_hours][enabled]" id="advanced-ads-ads-by-hours-enabled" <?php checked( ! empty( $options['enabled'] ) ); ?>>
<?php esc_html_e( 'Set specific hours', 'advanced-ads-pro' ); ?>
</label>
<?php if ( $this->module->use_browser_time() && ! $cache_busting_enabled ) : ?>
<div class="notice advads-notice inline" style="margin:5px 1px 7px">
<p><?php echo esc_html( $this->get_cb_warning_message() ); ?></p>
</div>
<?php endif; ?>
<?php if ( ! $this->module->use_browser_time() && Conditional::has_cache_plugins() && ! $cache_busting_enabled ) : ?>
<div class="notice advads-notice inline" style="margin:5px 1px 7px">
<p><?php echo esc_html( $this->get_cache_plugin_warning() ); ?></p>
</div>
<?php endif; ?>
<div class="inner" style="<?php echo ! empty( $options['enabled'] ) ? '' : 'display:none;'; ?>font-size:.9em;">
<div>
<p style="font-weight: 500;"><?php esc_html_e( 'From', 'advanced-ads-pro' ); ?></p>
<fieldset class="advads-timestamp">
<label>
<select name="advanced_ad[ads_by_hours][start_hour]">
<?php foreach ( $this->module->get_hours() as $hour ) : ?>
<option value="<?php echo esc_attr( $hour ); ?>" <?php selected( $hour, $options['start_hour'] ); ?>><?php echo esc_html( $this->get_localized_hour( $hour ) ); ?></option>
<?php endforeach; ?>
</select>
</label>:
<label>
<select name="advanced_ad[ads_by_hours][start_min]">
<?php foreach ( $this->module->get_minutes() as $min ) : ?>
<option value="<?php echo esc_attr( $min ); ?>" <?php selected( $min, $options['start_min'] ); ?>><?php echo esc_html( $min ); ?></option>
<?php endforeach; ?>
</select>
</label>
</fieldset>
</div>
<div>
<p style="font-weight: 500;"><?php esc_html_e( 'To', 'advanced-ads-pro' ); ?></p>
<fieldset class="advads-timestamp">
<label>
<select name="advanced_ad[ads_by_hours][end_hour]">
<?php foreach ( $this->module->get_hours() as $hour ) : ?>
<option value="<?php echo esc_attr( $hour ); ?>" <?php selected( $hour, $options['end_hour'] ); ?>><?php echo esc_html( $this->get_localized_hour( $hour ) ); ?></option>
<?php endforeach; ?>
</select>
</label>:
<label>
<select name="advanced_ad[ads_by_hours][end_min]">
<?php foreach ( $this->module->get_minutes() as $min ) : ?>
<option value="<?php echo esc_attr( $min ); ?>" <?php selected( $min, $options['end_min'] ); ?>><?php echo esc_html( $min ); ?></option>
<?php endforeach; ?>
</select>
</label>
</fieldset>
</div>
<p class="description">(<?php echo esc_html( $this->get_time_zone_string() ); ?>)</p>
</div>
</div>

View File

@@ -0,0 +1,174 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin class for the Ads for Adblockers module.
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
* @since 2.26.0
*/
use AdvancedAds\Options;
use AdvancedAds\Constants;
use AdvancedAds\Abstracts\Placement;
use AdvancedAds\Utilities\WordPress;
use AdvancedAds\Importers\XML_Importer;
/**
* Admin class
*/
class Advanced_Ads_Pro_Module_Ads_For_Adblockers_Admin {
/**
* Constructor
*/
public function __construct() {
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ] );
if ( ! Options::instance()->get( 'adblocker.ads-for-adblockers.enabled' ) ) {
return;
}
add_filter( 'advanced-ads-import-placement', [ $this, 'import_placement' ], 10, 2 );
add_action( 'advanced-ads-placement-options-after-advanced', [ $this, 'add_placement_setting' ], 10, 2 );
}
/**
* Initializes the settings
*
* @return void
*/
public function settings_init(): void {
add_settings_field(
'module-ads-for-adblockers',
__( 'Ads for ad blockers', 'advanced-ads-pro' ),
[ $this, 'render_settings' ],
ADVADS_SETTINGS_ADBLOCKER,
'advanced_ads_adblocker_setting_section'
);
}
/**
* Render 'Ads For Adblocker' settings
*
* @return void
*/
public function render_settings(): void {
$module_enabled = Options::instance()->get( 'adblocker.ads-for-adblockers.enabled' );
$cb_dashicon_class = ! empty( Advanced_Ads_Pro::get_instance()->get_options()['cache-busting']['enabled'] ) ? 'dashicons-yes advads-color-green' : 'dashicons-no color-red';
$ab_dashicon_class = Options::instance()->get( 'adblocker.use-adblocker' ) ? 'dashicons-yes advads-color-green' : 'dashicons-no color-red';
include_once __DIR__ . '/views/settings.php';
}
/**
* Render alternative item option.
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function add_placement_setting( $placement_slug, $placement ) {
$type_option = $placement->get_type_object()->get_options();
if ( isset( $type_option['placement-item-alternative'] ) && ! $type_option['placement-item-alternative'] ) {
return;
}
$options = Advanced_Ads_Pro::get_instance()->get_options();
$items = $this->items_for_select();
$messages = $this->get_messages( $placement );
$placement_data = $placement->get_data();
$cb_off = empty( $options['cache-busting']['enabled'] ) || ( isset( $placement_data['cache-busting'] ) && Advanced_Ads_Pro_Module_Cache_Busting::OPTION_OFF === $placement_data['cache-busting'] );
ob_start();
include __DIR__ . '/views/placement-item.php';
$item_option_content = ob_get_clean();
$ad_blocker_description = sprintf(
'%1s. %2s (<a href="%3s" target="_blank">%4s</a>)',
__( 'Displayed to visitors with an ad blocker', 'advanced-ads-pro' ),
__( 'Cache Busting and Ad blocker disguise need to be enabled', 'advanced-ads-pro' ),
esc_url( get_admin_url( '/', 'admin.php?page=advanced-ads-settings#top#pro' ) ),
__( 'Settings', 'advanced-ads-pro' )
);
WordPress::render_option(
'placement-item-alternative',
__( 'Ad blocker item', 'advanced-ads-pro' ),
$item_option_content,
$ad_blocker_description
);
}
/**
* Get items for item select field.
*
* @return array $select Items for select field.
*/
private function items_for_select() {
static $select = null;
// Check if result was cached.
if ( null !== $select ) {
return $select;
}
$select = [];
// Load all ads.
$ads = wp_advads_ad_query(
[
'order' => 'ASC',
'orderby' => 'title',
]
)->posts;
foreach ( $ads as $_ad ) {
$ad = wp_advads_get_ad( $_ad->ID );
if ( $ad->is_type( [ 'plain', 'content', 'image' ] ) ) {
$select['ads'][ Constants::ENTITY_AD . '_' . $_ad->ID ] = $_ad->post_title;
}
}
return $select;
}
/**
* Get messages related to selected alternative item.
*
* @param Placement $placement Placement instance.
*
* @return array $messages Array of strings.
*/
private function get_messages( $placement ) {
$messages = [];
if ( $placement ) {
$ad = Advanced_Ads_Pro_Module_Ads_For_Adblockers::get_item_for_adblocker( $placement );
if ( $ad ) {
$content = $ad->output();
if ( preg_match( '/<script[^>]+src=[\'"]/is', $content ) ) {
$messages[] .= __( 'The chosen ad contains a reference to an external .js file', 'advanced-ads-pro' );
}
}
}
return $messages;
}
/**
* Set an ad for adblocker during the import of a placement.
*
* @param array $placement Placement data.
* @param XML_Importer $import Import instance.
*
* @return array $placement
*/
public function import_placement( $placement, XML_Importer $import ) {
if ( ! empty( $placement['options']['item_adblocker'] ) ) {
$_item = explode( '_', $placement['options']['item_adblocker'] );
if ( ! empty( $_item[1] ) ) {
$found = $import->search_item( $_item[1], $_item[0] );
$placement['options']['item_adblocker'] = $found ? $_item[0] . '_' . $found : '';
}
}
return $placement;
}
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Ads_For_Adblockers_Admin();

View File

@@ -0,0 +1,202 @@
<?php // phpcs:ignore WordPress.Files.FileName
use AdvancedAds\Options;
use AdvancedAds\Constants;
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Abstracts\Group;
use AdvancedAds\Abstracts\Placement;
/**
* Get an ad that is delivered to users for ad blocker request.
*/
class Advanced_Ads_Pro_Module_Ads_For_Adblockers {
/**
* Holds unique identifiers of each chain. It allows to show only one copy of the alternative ad.
*
* @var array
*/
private $shown_chains = [];
/**
* Constructor
*/
public function __construct() {
// Early bail!!
if ( ! Options::instance()->get( 'adblocker.ads-for-adblockers.enabled' ) ) {
return;
}
add_filter( 'advanced-ads-pro-ad-needs-backend-request', [ $this, 'ad_needs_backend_request' ], 10, 3 );
if ( wp_doing_ajax() ) {
add_filter( 'advanced-ads-can-display-ad', [ $this, 'can_display' ], 10, 2 );
add_filter( 'advanced-ads-ad-select-args', [ $this, 'save_chain_id' ], 10, 1 );
add_filter( 'advanced-ads-ad-select-override-by-ad', [ $this, 'override_ad_select_by_ad' ], 10, 3 );
add_filter( 'advanced-ads-ad-select-override-by-group', [ $this, 'override_ad_select_by_group' ], 10, 4 );
}
}
/**
* Enable cache-busting if there is an ad for adblocker.
*
* @param string $check The original return value.
* @param Ad $ad The ad object.
* @param string $fallback The fallback value.
*
* @return string The value indicating if a backend request is needed ('passive') or the fallback value.
*/
public function ad_needs_backend_request( $check, Ad $ad, $fallback ) {
$ad_for_adblocker = self::get_item_for_adblocker( $ad );
if ( ! $ad_for_adblocker ) {
return $check;
}
if ( $check === $fallback ) {
return $fallback;
}
// AJAX or no cache-busting if PHP is enabled for ad for adblocker.
if ( $ad_for_adblocker->is_type( 'plain' ) && $ad_for_adblocker->is_php_allowed() ) {
return $fallback;
}
return 'passive';
}
/**
* Save chain id.
*
* @param array $args Ad arguments.
*
* @return array $args
*/
public function save_chain_id( $args ) {
if ( ! isset( $args['chain_id'] ) ) {
$args['chain_id'] = wp_rand();
}
return $args;
}
/**
* Check if the ad can be displayed.
*
* @param bool $can_display Can the ad be displayed.
* @param Ad $ad Ad instance.
*
* @return bool
*/
public function can_display( $can_display, Ad $ad ) {
if ( ! $can_display ) {
return $can_display;
}
if ( in_array( $ad->get_prop( 'chain_id' ), $this->shown_chains, true ) ) {
return false;
}
return true;
}
/**
* Overrides the selected ad with a new ad based on certain conditions.
*
* @param string $overriden_ad The original selected ad.
* @param AD $ad Ad instance.
* @param array $args Additional arguments.
*
* @return string The overridden ad output.
*/
public function override_ad_select_by_ad( $overriden_ad, AD $ad, $args ) {
return $this->override_ad_select( $overriden_ad, $ad, $args );
}
/**
* Overrides the selected ad with a new ad based on certain conditions.
*
* @param string $overriden_group The original selected ad.
* @param Group $group Group instance.
* @param array|null $ordered_ad_ids ordered ids of the ads that belong to the group.
* @param array $args Additional arguments.
*
* @return string The overridden ad output.
*/
public function override_ad_select_by_group( $overriden_group, Group $group, $ordered_ad_ids, $args ) {
return $this->override_ad_select( $overriden_group, $group, $args );
}
/**
* Common logic for overriding the selected ad or group.
*
* @param string $overriden_item The original selected ad or group.
* @param Ad|Group $entity Ad or Group instance.
* @param array $args Additional arguments.
*
* @return string The overridden ad or group output.
*/
private function override_ad_select( $overriden_item, $entity, $args ) {
if ( ! $entity->can_display() || empty( $args['adblocker_active'] ) || empty( $args['item_adblocker'] ) ) {
return $overriden_item;
}
$_item = explode( '_', $args['item_adblocker'] );
if ( ! empty( $_item[1] ) ) {
if ( in_array( $_item[0], [ Constants::ENTITY_AD, 'id' ], true ) ) {
$ab_ad = wp_advads_get_ad( $_item[1] );
$ab_ad->set_parent( $entity->get_root_placement() );
$overriden_item = $ab_ad->output();
$this->shown_chains[] = $args['chain_id'];
} elseif ( Constants::ENTITY_GROUP === $_item[0] ) {
$group = wp_advads_get_group( (int) $_item[1] );
$group->set_parent( $entity->get_root_placement() );
$overriden_item = $group->output();
}
}
return $overriden_item;
}
/**
* Get an ad that is delivered to users with an ad blocker enabled.
*
* @param Ad|Placement $entity Ad or placement instance.
*
* @return Ad|Group|bool bool false; Ad or Group if an item for ad blocker is found.
*/
public static function get_item_for_adblocker( $entity ) {
// Early bail!!
if ( ! Options::instance()->get( 'adblocker.ads-for-adblockers.enabled' ) ) {
return false;
}
$placement = is_a_placement( $entity ) ? $entity : $entity->get_root_placement();
if ( empty( $placement ) || empty( $placement->get_prop( 'item_adblocker' ) ) ) {
return false;
}
$_item = explode( '_', $placement->get_prop( 'item_adblocker' ) );
if ( ! empty( $_item[1] ) ) {
$item_id = absint( $_item[1] );
if ( in_array( $_item[0], [ Constants::ENTITY_AD, 'id' ], true ) ) {
$ad = wp_advads_get_ad( $item_id );
if ( $ad ) {
$ad->set_parent( $placement );
return $ad;
}
} elseif ( Constants::ENTITY_GROUP === $_item[0] ) {
$group = wp_advads_get_group( $item_id );
if ( $group ) {
foreach ( $group->get_ads() as $ad ) {
$ad->set_parent( $placement );
}
return $group;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,2 @@
<?php
new Advanced_Ads_Pro_Module_Ads_For_Adblockers();

View File

@@ -0,0 +1,47 @@
<?php
/**
* Placement adblocker item dropdown
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
use AdvancedAds\Constants;
$groups = wp_advads_get_all_groups();
?>
<div class="advanced-ads-inputs-dependent-on-cb" <?php echo $cb_off ? 'style="display:none;"' : null; ?>>
<select id="advads-placements-item-adblocker-<?php echo esc_attr( $placement_slug ); ?>" name="advads[placements][options][item_adblocker]">
<option value=""><?php esc_html_e( '--not selected--', 'advanced-ads-pro' ); ?></option>
<?php if ( ! empty( $groups ) ) : ?>
<optgroup label="<?php esc_html_e( 'Groups', 'advanced-ads-pro' ); ?>">
<?php foreach ( $groups as $group ) : ?>
<option value="<?php echo esc_attr( Constants::ENTITY_GROUP . '_' . $group->get_id() ); ?>"<?php selected( Constants::ENTITY_GROUP . '_' . $group->get_id(), $placement_data['item_adblocker'] ?? '' ); ?>>
<?php echo esc_html( $group->get_name() ); ?>
<?php endforeach; ?>
</optgroup>
<?php endif; ?>
<?php if ( isset( $items['ads'] ) ) : ?>
<optgroup label="<?php esc_html_e( 'Ads', 'advanced-ads-pro' ); ?>">
<?php foreach ( $items['ads'] as $_item_id => $_item_title ) : ?>
<option value="<?php echo esc_attr( $_item_id ); ?>"<?php selected( $_item_id, $placement_data['item_adblocker'] ?? '' ); ?>>
<?php echo esc_html( $_item_title ); ?>
</option>
<?php endforeach; ?>
</optgroup>
<?php endif; ?>
</select>
<?php if ( $messages ) : ?>
<?php foreach ( $messages as $_message ) : ?>
<p class="advads-notice-inline advads-error">
<?php echo esc_html( $_message ); ?>
</p>
<?php endforeach; ?>
<?php endif; ?>
</div>
<p class="advads-notice-inline advads-idea" <?php echo ! $cb_off ? 'style="display:none;"' : null; ?>>
<?php esc_html_e( 'Works only with cache-busting enabled', 'advanced-ads-pro' ); ?>
</p>

View File

@@ -0,0 +1,33 @@
<?php
/**
* Render 'Ads For Adblocker' settings
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
* @since 2.26.0
*
* @var array $options Advanced Ads Pro options.
* @var bool $module_enabled if the ads-for-adblockers module is enabled.
* @var string $cb_dashicon_class CSS class for Cache Busting on or off.
* @var string $ab_dashicon_class CSS class for Ad block disguise on or off.
*/
?>
<input name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER ); ?>[ads-for-adblockers][enabled]" id="advanced-ads-pro-ads-for-adblockers-enabled" type="checkbox" value="1" class="advads-has-sub-settings" <?php checked( $module_enabled ); ?> />
<label for="advanced-ads-pro-ads-for-adblockers-enabled" class="description">
<?php esc_html_e( 'Activate module.', 'advanced-ads-pro' ); ?>
</label>
<a href="https://wpadvancedads.com/manual/ad-blockers/?utm_source=advanced-ads&utm_medium=link&utm_campaign=pro-ab-manual" target="_blank" class="advads-manual-link"><?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?></a>
<div class="advads-sub-settings">
<p class="description">
<?php
echo wp_kses_post(
__( 'This module requires:', 'advanced-ads-pro' )
. '<br> <span class="dashicons ' . esc_attr( $cb_dashicon_class ) . '"></span>'
. __( 'Cache Busting', 'advanced-ads-pro' )
. '<br> <span class="dashicons ' . esc_attr( $ab_dashicon_class ) . '"></span>'
. __( 'Ad block disguise', 'advanced-ads-pro' )
);
?>
</p>
</div>

View File

@@ -0,0 +1,5 @@
<?php
class Advanced_Ads_Pro_Module_Advanced_Display_Conditions_Admin {
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Advanced_Display_Conditions_Admin();

View File

@@ -0,0 +1,599 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Framework\Utilities\Params;
/**
* The Advanced Display Conditions module.
*
* TODO should use a constant for option key as it is shared at multiple positions.
*/
class Advanced_Ads_Pro_Module_Advanced_Display_Conditions {
protected $options = [];
protected $is_ajax;
public function __construct() {
add_filter( 'advanced-ads-display-conditions', [ $this, 'display_conditions' ] );
$this->is_ajax = wp_doing_ajax();
if ( ! $this->is_ajax ) {
// attach more ad select values
add_filter( 'advanced-ads-ad-select-args', [ $this, 'additional_ad_select_args' ] );
}
}
/**
* Add visitor condition.
*
* @since 1.0.1
* @param array $conditions Display conditions of the main plugin.
* @return array $conditions New global visitor conditions.
*/
public function display_conditions( $conditions ){
// current uri
$conditions['request_uri'] = [
'label' => __( 'url parameters', 'advanced-ads-pro' ),
/* translators: %s: URL */
'description' => sprintf(__( 'Display ads based on the current URL parameters (everything following %s), except values following #.', 'advanced-ads-pro' ), ltrim( home_url(), '/' ) ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_string' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_request_uri' ], // callback for frontend check
'helplink' => 'https://wpadvancedads.com/manual/ads-by-url-parameters/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-url-parameter',
];
/** page template, see https://developer.wordpress.org/themes/template-files-section/page-template-files/page-templates/
* in WP 4.7, this logic was extended to also support templates
* for other post types, hence we now add a condition for all
* other post types with registered templates
*
*/
$conditions['page_template'] = [
/* translators: %s: page */
'label' => sprintf(__( '%s template', 'advanced-ads-pro' ), 'page' ),
/* translators: %s: post type */
'description' => sprintf(__( 'Display ads based on the template of the %s post type.', 'advanced-ads-pro' ), 'page' ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_page_template' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_page_template' ], // callback for frontend check
'post-type' => 'page'
];
/**
* load post templates
* need to check, because only works with WP 4.7 and higher
*/
if( method_exists( 'WP_Theme', 'get_post_templates' ) ){
$page_templates = wp_get_theme()->get_post_templates();
if( is_array( $page_templates ) ){
foreach( $page_templates as $_post_type => $_templates ){
// skip page templates, because they are already registered and another index would cause old conditions to not work anymore
if( 'page' === $_post_type ){
continue;
}
$conditions['page_template_' . $_post_type ] = [
'label' => sprintf(__( '%s template', 'advanced-ads-pro' ), $_post_type ),
'description' => sprintf(__( 'Display ads based on the template of the %s post type.', 'advanced-ads-pro' ), $_post_type ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_page_template' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_page_template' ], // callback for frontend check
'post-type' => $_post_type
];
}
}
}
// language set with the WPML plugin
if( defined( 'ICL_SITEPRESS_VERSION' ) ) {
$conditions['wpml_language'] = [
'label' => __( 'WPML language', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the page language set by the WPML plugin.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_wpml_language' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_wpml_language' ] // callback for frontend check
];
}
$conditions['sub_page'] = [
'label' => __( 'parent page', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on parent page.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Display_Conditions', 'metabox_post_ids' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_parent_page' ] // callback for frontend check
];
$conditions['post_meta'] = [
'label' => __( 'post meta', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on post meta.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_post_meta' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_post_meta' ] // callback for frontend check
];
$conditions['paginated_post'] = [
'label' => __( 'pagination', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the index of a split page', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_paginated_post' ], // callback to generate the metabox
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_paginated_post' ], // callback for frontend check
];
$conditions['post_content'] = [
'label' => __( 'post content', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on words and phrases within the post or page content. Dynamically added text might not be considered.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'metabox_string' ], // callback to generate the metabox.
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Display_Conditions', 'check_post_content' ], // callback for frontend check.
'helplink' => 'https://wpadvancedads.com/manual/ads-based-on-keywords/?utm_source=advanced-ads&utm_medium=link?utm_campaign=condition-post-content',
];
return $conditions;
}
/**
* add ad select vars that can later be used by ajax
*
* @since untagged
* @param array $args Arguments passed to ads and groups from top level placements/ads/groups.
* @return array
*/
public function additional_ad_select_args( $args ) {
if ( isset( $args['url_parameter'] ) ) {
return $args;
}
$referer = Params::server( 'HTTP_REFERER' );
if ( wp_doing_ajax() && $referer ) {
// An AJAX request to load content initiated by a third party plugin.
$args['url_parameter'] = $referer;
} else {
$args['url_parameter'] = Params::server( 'REQUEST_URI', '' );
// Only consider QUERY_STRING, if not already included in REQUEST_URI.
$query_string = Params::server( 'QUERY_STRING', '' );
if ( ! empty( $query_string ) && false === strpos( Params::server( 'REQUEST_URI' ), $query_string ) ) {
$args['url_parameter'] .= $query_string;
}
}
/**
* Allow other developers to manipulate the checked string dynamically.
*
* @param string $args['url_parameter'] Current URL parameter.
* @return string
*/
$args['url_parameter'] = apply_filters( 'advanced-ads-pro-display-condition-url-string', $args['url_parameter'] );
return $args;
}
/**
* Check if ad can be displayed by request_uri condition in frontend.
*
* @param array $options options of the condition.
* @param Ad $ad The ad object.
*
* @return bool
*/
public static function check_request_uri( $options, Ad $ad ) {
$uri_string = $ad->get_prop( 'ad_args.url_parameter' ) ?? '';
// todo: implement this method into display conditions
return Advanced_Ads_Visitor_Conditions::helper_check_string( $uri_string, $options );
}
/**
* Check if ad can be displayed by page template condition in frontend.
*
* @param array $options Options of the condition.
* @param Ad $ad The ad object.
*
* @return bool
*/
public static function check_page_template( $options, Ad $ad ) {
// Early bail!!
if ( ! isset( $options['value'] ) || ! is_array( $options['value'] ) ) {
return false;
}
$operator = 'is_not' === ( $options['operator'] ?? '' ) ? 'is_not' : 'is';
$ad_options = $ad->get_data();
$post = $ad_options['post'] ?? null;
if ( null === $post ) {
return false;
}
$post_template = get_page_template_slug( $post['id'] ) ?? '';
if ( ! Advanced_Ads_Display_Conditions::can_display_ids( $post_template, $options['value'], $operator ) ) {
return false;
}
return true;
}
/**
* Check if ad can be displayed by WPML language condition in frontend.
*
* @param array $options Options of the condition.
* @param Ad $ad Ad.
*
* @return bool
*/
public static function check_wpml_language( $options, Ad $ad ) {
if (!isset($options['value']) || !is_array($options['value'])) {
return false;
}
$operator = 'is';
if (isset($options['operator']) && $options['operator'] === 'is_not') {
$operator = 'is_not';
}
$lang = apply_filters( 'wpml_current_language', null );
if ( ! Advanced_Ads_Display_Conditions::can_display_ids( $lang, $options['value'], $operator ) ) {
return false;
}
return true;
}
/**
* Check if ad can be displayed by 'is sub-page' condition in frontend.
*
* @param array $options Options of the condition.
* @param Ad $ad The ad object.
*
* @return bool
*/
public static function check_parent_page( $options, Ad $ad ) {
if ( ! isset($options['value']) || ! is_array( $options['value'] ) ) {
return false;
}
$operator = 'is';
if ( isset( $options['operator'] ) && $options['operator'] === 'is_not' ) {
$operator = 'is_not';
}
$post = Advanced_Ads_Pro_Utils::get_post();
$post_parent = ! empty( $post->post_parent ) ? $post->post_parent : 0;
if ( ! Advanced_Ads_Display_Conditions::can_display_ids( $post_parent, $options['value'], $operator ) ) {
return false;
}
return true;
}
/**
* Check if ad can be displayed by 'post meta' condition in frontend.
*
* @param array $options Options of the condition.
* @param Ad $ad The ad object.
*
* @return bool
*/
public static function check_post_meta( $options, Ad $ad ) {
$post = Advanced_Ads_Pro_Utils::get_post();
$mode = ( isset($options['mode']) && $options['mode'] === 'all' ) ? 'all' : 'any';
$meta_field = isset( $options['meta_field'] ) ? $options['meta_field'] : '';
if ( empty( $post->ID ) && empty( $meta_field ) ) {
return true;
}
$meta_values = get_post_meta( $post->ID, $meta_field );
if ( ! $meta_values ) {
// allow *_not operators return true
return Advanced_Ads_Visitor_Conditions::helper_check_string( '', $options );
}
foreach ( $meta_values as $_meta_value ) {
$result = Advanced_Ads_Visitor_Conditions::helper_check_string( $_meta_value, $options );
if ( ! $result && 'all' === $mode ) {
return false;
}
if ( $result && 'any' === $mode ) {
return true;
}
}
return 'all' === $mode;
}
/**
* Check if ad can be displayed by 'paginated post' condition in frontend.
*
* @param array $options Options of the condition.
* @param Ad $ad The ad object.
*
* @return bool
*/
public static function check_paginated_post( $options, Ad $ad ) {
if ( ! isset( $options['first'] ) || ! isset( $options['last'] ) ) {
return false;
}
$ad_options = $ad->get_data();
if ( ! isset( $ad_options['wp_the_query']['page'] ) || ! isset( $ad_options['wp_the_query']['numpages'] ) ) {
return false;
}
$first = ! empty( $options['first'] ) ? absint( $options['first'] ) : 1;
$last = ! empty( $options['last'] ) ? absint( $options['last'] ) : 1;
$page = $ad_options['wp_the_query']['page'];
$numpages = $ad_options['wp_the_query']['numpages'];
if ( ! empty( $options['count_from_end'] ) ) {
$first = $numpages + 1 - $first;
$last = $numpages + 1 - $last;
}
if ( $first > $last ) {
$tmp = $first;
$first = $last;
$last = $tmp;
}
if ( $page < $first || $page > $last ) {
return false;
}
return true;
}
/**
* Check "Post content" display condition in frontend.
*
* @param array $options The options of the condition.
* @param Ad $ad The ad object.
* @return bool true if ad can be displayed.
*/
public static function check_post_content( $options, Ad $ad ) {
$ad_args = $ad->get_prop( 'ad_args' );
if ( ! isset( $ad_args['post']['id'] ) ) {
return true;
}
$content = get_the_content( null, false, (int) $ad_args['post']['id'] );
// Since we want to remove only this function, we do not use `__return_false`.
$return_false = function() {
return false;
};
// Also disable ad/group/placement shortcodes in the content.
Advanced_Ads_Pro::get_instance()->remove_shortcodes();
add_filter( 'advanced-ads-can-inject-into-content', $return_false );
$content = apply_filters( 'the_content', $content );
remove_filter( 'advanced-ads-can-inject-into-content', $return_false );
Advanced_Ads_Pro::get_instance()->add_shortcodes();
$content = wp_strip_all_tags( $content );
return Advanced_Ads_Visitor_Conditions::helper_check_string( $content, $options );
}
/**
* callback to display any condition based on a string
*
* @since 1.4
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_string( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Display_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$value = isset( $options['value'] ) ? $options['value'] : '';
$operator = isset( $options['operator'] ) ? $options['operator'] : 'contains';
include dirname( __FILE__ ) . '/views/metabox-string.php';
}
/**
* callback to display the page templates condition
*
* @since 1.4.1
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_page_template( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Display_Conditions::get_instance()->conditions;
if (!isset($type_options[$options['type']])) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$values = ( isset($options['value']) && is_array($options['value']) ) ? $options['value'] : [];
$operator = ( isset($options['operator']) && $options['operator'] === 'is_not' ) ? 'is_not' : 'is';
// get values and select operator based on previous settings
?><input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<select name="<?php echo $name; ?>[operator]">
<option value="is" <?php selected('is', $operator); ?>><?php _e('is', 'advanced-ads-pro'); ?></option>
<option value="is_not" <?php selected('is_not', $operator); ?>><?php _e('is not', 'advanced-ads-pro'); ?></option>
</select><?php
// get all page templates
$post_type = ( isset( $type_options[$options['type']]['post-type'] ) ) ? $type_options[$options['type']]['post-type'] : 'page';
$templates = get_page_templates( null, $post_type );
$rand = md5( $form_name );
?><div class="advads-conditions-single advads-buttonset"><?php
foreach( $templates as $_name => $_file ) {
if (isset( $values ) && is_array( $values ) && in_array( $_file, $values ) ) {
$_val = 1;
} else {
$_val = 0;
}
$field_id = 'advads-conditions-' . sanitize_title( $_name ) . md5( $name );
?><label class="button ui-button" for="<?php echo $field_id;
?>"><?php echo $_name; ?></label><input type="checkbox" id="<?php echo $field_id; ?>" name="<?php echo $name; ?>[value][]" <?php checked($_val, 1); ?> value="<?php echo $_file; ?>"><?php
}
if ( file_exists( ADVADS_ABSPATH . 'admin/views/conditions/not-selected.php' ) ) {
include ADVADS_ABSPATH . 'admin/views/conditions/not-selected.php';
}
?></div>
<p class="description"><?php echo $type_options[ $options['type'] ]['description']; ?></p><?php
}
/**
* callback to display the WPML language condition
*
* @since 1.8*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_wpml_language( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Display_Conditions::get_instance()->conditions;
if (!isset($type_options[$options['type']])) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$values = ( isset($options['value']) && is_array($options['value']) ) ? $options['value'] : [];
$operator = ( isset($options['operator']) && $options['operator'] === 'is_not' ) ? 'is_not' : 'is';
// get values and select operator based on previous settings
?><input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<select name="<?php echo $name; ?>[operator]">
<option value="is" <?php selected('is', $operator); ?>><?php _e('is', 'advanced-ads-pro'); ?></option>
<option value="is_not" <?php selected('is_not', $operator); ?>><?php _e('is not', 'advanced-ads-pro'); ?></option>
</select><?php
// get all languages
$wpml_active_languages = apply_filters( 'wpml_active_languages', null, [] );
$rand = md5( $form_name );
?><div class="advads-conditions-single advads-buttonset"><?php
if( is_array( $wpml_active_languages ) && count( $wpml_active_languages ) ){
foreach( $wpml_active_languages as $_language ) {
$field_id = 'advads-conditions-' . $_language['code'] . md5( $name );
$value = ( $values === [] || in_array($_language['code'], $values) ) ? 1 : 0;
?><label class="button ui-button" for="<?php echo $field_id;
?>"><?php echo $_language['native_name']; ?></label><input type="checkbox" id="<?php echo $field_id; ?>" name="<?php echo $name; ?>[value][]" <?php checked($value, 1); ?> value="<?php echo $_language['code']; ?>"><?php
}
} else {
_e( 'no languages set up in WPML', 'advanced-ads-pro' );
}
?></div>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="https://wpadvancedads.com/translating-ads-wpml/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-wpml" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
<?php
}
/**
* Callback to display the 'post meta' condition
*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_post_meta( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Display_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$mode = ( isset($options['mode']) && $options['mode'] === 'all' ) ? 'all' : 'any';
$operator = isset( $options['operator'] ) ? $options['operator'] : 'contains';
$meta_field = isset( $options['meta_field'] ) ? $options['meta_field'] : '';
$value = isset( $options['value'] ) ? $options['value'] : '';
?><select name="<?php echo $name; ?>[mode]">
<option value="any" <?php selected( 'any', $mode ); ?>><?php _e( 'any of', 'advanced-ads-pro'); ?></option>
<option value="all" <?php selected( 'all', $mode ); ?>><?php _e( 'all of', 'advanced-ads-pro' ); ?></option>
</select><input type="text" name="<?php echo $name; ?>[meta_field]" value="<?php echo $meta_field; ?>" placeholder="<?php _e( 'meta key', 'advanced-ads-pro' ); ?>"/><?php
include dirname( __FILE__ ) . '/views/metabox-string.php';
}
/**
* Callback to display the 'paginated post' condition
*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_paginated_post( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Display_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$first = ! empty( $options['first'] ) ? Advanced_Ads_Pro_Utils::absint( $options['first'], 1 ) : 1;
$last = ! empty( $options['last'] ) ? Advanced_Ads_Pro_Utils::absint( $options['last'], 1 ) : 1;
$count_from_end = ! empty( $options['count_from_end'] );
$first_field = '<input type="number" required="required" min="1" name="' . $name . '[first]" value="' . $first . '"/>.';
$last_field = '<input type="number" required="required" min="1" name="' . $name . '[last]" value="' . $last . '"/>.';
/* translators: %s: first field, %s: last field */
printf( __( 'from %1$s to %2$s', 'advanced-ads-pro' ), $first_field, $last_field ); ?> <input id="advads-conditions-<?php
echo $index; ?>-count-from-end" type="checkbox" value="1" name="<?php
echo $name; ?>[count_from_end]" <?php checked( $count_from_end, 1 ); ?>><label for="advads-conditions-<?php
echo $index; ?>-count-from-end"><?php _e( 'count from end', 'advanced-ads-pro' ); ?></label>
<input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<p class="description"><?php echo $type_options[ $options['type'] ]['description']; ?></p>
<?php
}
/**
* Helper function to the name of a form field.
* falls back to default
*
* @param string $form_name form name if submitted.
* @param int $index index of the condition.
*
* @return string
*/
public static function get_form_name_with_index( $form_name = '', $index = 0 ) {
// form name basis
if ( method_exists( 'Advanced_Ads_Display_Conditions', 'get_form_name_with_index' ) ) {
return Advanced_Ads_Display_Conditions::get_form_name_with_index( $form_name, $index );
} else {
return Advanced_Ads_Display_Conditions::FORM_NAME . '[' . $index . ']';
}
}
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Advanced_Display_Conditions;

View File

@@ -0,0 +1,15 @@
<div class="advads-condition-line-wrap">
<input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<?php if( 0 <= version_compare( ADVADS_VERSION, '1.9.1' ) ) {
include( ADVADS_ABSPATH . 'admin/views/ad-conditions-string-operators.php' );
} ?>
<input type="text" name="<?php echo $name; ?>[value]" value="<?php echo $value; ?>"/>
</div>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<?php if ( isset( $type_options[ $options['type'] ]['helplink'] ) ) : ?>
<a href="<?php echo esc_url( $type_options[ $options['type'] ]['helplink'] ) ?>" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
<?php endif; ?>
</p>

View File

@@ -0,0 +1,16 @@
<h4><?php _e( 'Display by referrer url', 'advanced-ads-pro' ); ?></h4>
<p><label><input type="checkbox" name="advanced_ad[visitor][referrer-url][enable]" value="1" <?php
checked( $ref_url_enable, 1 ); ?> onclick="advads_toggle_box(this, '#advads-referer-url');"/><?php _e( 'Display ad depending on the external url the user comes from.', 'advanced-ads-pro' ); ?></label></p>
<div id="advads-referer-url"<?php if ( ! $ref_url_enable ) : ?> style="display:none;"<?php endif; ?>>
<?php _e( 'URL', 'advanced-ads-pro' ); ?>
<select name="advanced_ad[visitor][referrer-url][bool]"><?php
foreach ( $referrer_url_bools as $_bool_key => $_bool ) : ?>
<option value="<?php echo $_bool_key; ?>" <?php selected( $_bool_key, $ref_url_bool ); ?>><?php echo $_bool; ?></option>
<?php endforeach; ?></select>
<select name="advanced_ad[visitor][referrer-url][mode]"><?php
foreach ( $referrer_url_modi as $_mod_key => $_modus ) : ?>
<option value="<?php echo $_mod_key; ?>" <?php selected( $_mod_key, $ref_url_mode ); ?>><?php echo $_modus; ?></option>
<?php endforeach; ?></select>
<input type="text" name="advanced_ad[visitor][referrer-url][url]" value="<?php
echo $ref_url_url; ?>" placeholder="<?php _e( 'url of the referrer', 'advanced-ads-pro' ); ?>"/>
</div>

View File

@@ -0,0 +1,44 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin class for the Advanced Ads Pro Advanced Visitor Conditions module.
*
* @package AdvancedAds\Pro\Modules\Visitor_Conditions
* @author Advanced Ads <info@wpadvancedads.com>
*/
/**
* Class Visitor_Conditions
*/
class Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions_Admin {
/**
* The constructor
*/
public function __construct() {
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ] );
}
/**
* Add settings for the module.
*
* @return void
*/
public function settings_init(): void {
add_settings_field(
'module-advanced-visitor-conditions',
__( 'Advanced visitor conditions', 'advanced-ads-pro' ),
[ $this, 'render_settings' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings',
Advanced_Ads_Pro::OPTION_KEY . '_modules-enable'
);
}
/**
* Render the settings.
*
* @return void
*/
public function render_settings(): void {
include_once __DIR__ . '/views/settings.php';
}
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions_Admin();

View File

@@ -0,0 +1,203 @@
<?php
$advads_browser_langs = [
'af' => 'Afrikaans',
'ar' => 'Arabic',
'ar-ae' => 'Arabic (U.A.E.)',
'ar-bh' => 'Arabic (Bahrain)',
'ar-dz' => 'Arabic (Algeria)',
'ar-eg' => 'Arabic (Egypt)',
'ar-iq' => 'Arabic (Iraq)',
'ar-jo' => 'Arabic (Jordan)',
'ar-kw' => 'Arabic (Kuwait)',
'ar-lb' => 'Arabic (Lebanon)',
'ar-ly' => 'Arabic (Libya)',
'ar-ma' => 'Arabic (Morocco)',
'ar-om' => 'Arabic (Oman)',
'ar-qa' => 'Arabic (Qatar)',
'ar-sa' => 'Arabic (Saudi Arabia)',
'ar-sy' => 'Arabic (Syria)',
'ar-tn' => 'Arabic (Tunisia)',
'ar-ye' => 'Arabic (Yemen)',
'ar' => 'Aragonese',
'as' => 'Assamese',
'ast' => 'Asturian',
'az' => 'Azerbaijani',
'be' => 'Belarusian',
'bg' => 'Bulgarian',
'bg' => 'Bulgarian',
'bn' => 'Bengali',
'br' => 'Breton',
'bs' => 'Bosnian',
'ca' => 'Catalan',
'ce' => 'Chechen',
'ch' => 'Chamorro',
'co' => 'Corsican',
'cr' => 'Cree',
'cs' => 'Czech',
'cv' => 'Chuvash',
'cy' => 'Welsh',
'da' => 'Danish',
'de' => 'German',
'de-at' => 'German (Austria)',
'de-ch' => 'German (Switzerland)',
'de-de' => 'German (Germany)',
'de-li' => 'German (Liechtenstein)',
'de-lu' => 'German (Luxembourg)',
'el' => 'Greek',
'en' => 'English',
'en-au' => 'English (Australia)',
'en-bz' => 'English (Belize)',
'en-ca' => 'English (Canada)',
'en-gb' => 'English (United Kingdom)',
'en-ie' => 'English (Ireland)',
'en-jm' => 'English (Jamaica)',
'en-nz' => 'English (New Zealand)',
'en-ph' => 'English (Philippines)',
'en-tt' => 'English (Trinidad & Tobago)',
'en-us' => 'English (United States)',
'en-za' => 'English (South Africa)',
'en-zw' => 'English (Zimbabwe)',
'eo' => 'Esperanto',
'es' => 'Spanish',
'es-ar' => 'Spanish (Argentina)',
'es-bo' => 'Spanish (Bolivia)',
'es-cl' => 'Spanish (Chile)',
'es-co' => 'Spanish (Colombia)',
'es-cr' => 'Spanish (Costa Rica)',
'es-do' => 'Spanish (Dominican Republic)',
'es-ec' => 'Spanish (Ecuador)',
'es-es' => 'Spanish (Spain)',
'es-gt' => 'Spanish (Guatemala)',
'es-hn' => 'Spanish (Honduras)',
'es-mx' => 'Spanish (Mexico)',
'es-ni' => 'Spanish (Nicaragua)',
'es-pa' => 'Spanish (Panama)',
'es-pe' => 'Spanish (Peru)',
'es-pr' => 'Spanish (Puerto Rico)',
'es-py' => 'Spanish (Paraguay)',
'es-sv' => 'Spanish (El Salvador)',
'es-uy' => 'Spanish (Uruguay)',
'es-ve' => 'Spanish (Venezuela)',
'et' => 'Estonian',
'eu' => 'Basque',
'fa-ir' => 'Persian/Iran',
'fa' => 'Farsi',
'fa' => 'Persian',
'fi' => 'Finnish',
'fj' => 'Fijian',
'fo' => 'Faeroese',
'fr' => 'French',
'fr-be' => 'French (Belgium)',
'fr-ca' => 'French (Canada)',
'fr-ch' => 'French (Switzerland)',
'fr-fr' => 'French (France)',
'fr-lu' => 'French (Luxembourg)',
'fr-mc' => 'French (Monaco)',
'fur' => 'Friulian',
'fy' => 'Frisian',
'ga' => 'Irish',
'gd-ie' => 'Gaelic (Irish)',
'gd' => 'Gaelic (Scots)',
'gd' => 'Scots Gaelic',
'gl' => 'Galacian',
'gu' => 'Gujurati',
'he' => 'Hebrew',
'hi' => 'Hindi',
'hr' => 'Croatian',
'hsb' => 'Upper Sorbian',
'ht' => 'Haitian',
'hu' => 'Hungarian',
'hy' => 'Armenian',
'id' => 'Indonesian',
'is' => 'Icelandic',
'it' => 'Italian',
'it-ch' => 'Italian (Switzerland)',
'iu' => 'Inuktitut',
'ja' => 'Japanese',
'ji' => 'Yiddish',
'ka' => 'Georgian',
'kk' => 'Kazakh',
'km' => 'Khmer',
'kn' => 'Kannada',
'ko' => 'Korean',
'ko-kp' => 'Korean (North Korea)',
'ko-kr' => 'Korean (South Korea)',
'ks' => 'Kashmiri',
'ky' => 'Kirghiz',
'la' => 'Latin',
'lb' => 'Luxembourgish',
'lt' => 'Lithuanian',
'lv' => 'Latvian',
'mi' => 'Maori',
'mk' => 'FYRO Macedonian',
'ml' => 'Malayalam',
'mo' => 'Moldavian',
'mr' => 'Marathi',
'ms' => 'Malay',
'mt' => 'Maltese',
'my' => 'Burmese',
'nb' => 'Norwegian (Bokmal)',
'ne' => 'Nepali',
'ng' => 'Ndonga',
'nl' => 'Dutch',
'nl-be' => 'Dutch (Belgian)',
'nn' => 'Norwegian (Nynorsk)',
'no' => 'Norwegian',
'nv' => 'Navajo',
'oc' => 'Occitan',
'om' => 'Oromo',
'or' => 'Oriya',
'pa' => 'Punjabi',
'pa-in' => 'Punjabi (India)',
'pa-pk' => 'Punjabi (Pakistan)',
'pl' => 'Polish',
'pt' => 'Portuguese',
'pt-br' => 'Portuguese (Brazil)',
'qu' => 'Quechua',
'rm' => 'Rhaeto-Romanic',
'ro' => 'Romanian',
'ro-mo' => 'Romanian (Moldavia)',
'ru-mo' => 'Russian (Moldavia)',
'ru' => 'Russian',
'sa' => 'Sanskrit',
'sb' => 'Sorbian',
'sc' => 'Sardinian',
'sd' => 'Sindhi',
'sg' => 'Sango',
'si' => 'Singhalese',
'sk' => 'Slovak',
'sl' => 'Slovenian',
'so' => 'Somani',
'sq' => 'Albanian',
'sr' => 'Serbian',
'sv' => 'Swedish',
'sv-fi' => 'Swedish (Finland)',
'sv-sv' => 'Swedish (Sweden)',
'sw' => 'Swahili',
'sx' => 'Sutu',
'sz' => 'Sami (Lappish)',
'ta' => 'Tamil',
'te' => 'Teluga',
'th' => 'Thai',
'tig' => 'Tigre',
'tk' => 'Turkmen',
'tlh' => 'Klingon',
'tn' => 'Tswana',
'tr' => 'Turkish',
'ts' => 'Tsonga',
'tt' => 'Tatar',
'uk' => 'Ukrainian',
'ur' => 'Urdu',
've' => 'Venda',
'vi' => 'Vietnamese',
'vo' => 'Volapuk',
'wa' => 'Walloon',
'xh' => 'Xhosa',
'zh' => 'Chinese',
'zh-cn' => 'Chinese (PRC)',
'zh-hk' => 'Chinese (Hong Kong)',
'zh-sg' => 'Chinese (Singapore)',
'zh-tw' => 'Chinese (Taiwan)',
'zu' => 'Zulu'
];

View File

@@ -0,0 +1,96 @@
(function() {
if ( typeof advanced_ads_pro_visitor_conditions !== 'object' ) {
return;
}
/**
* The cookie storage object.
*
* Since we cannot read expiration times of cookies, we use our own `expires` field to save expiration times.
* This allows us to update cookies without updating their expiration times, i.e. without prolonging them.
*
* @param {string} name The cookie name.
* @param {int} exdays The number of days before the cookie expires.
*/
function cookie_storage( name, exdays ) {
this.name = name;
this.exdays = exdays;
this.data = undefined;
this.expires = 0;
var cookie = advads.get_cookie( name );
if ( ! cookie ) {
this.data = cookie;
return;
}
try {
var cookie_obj = JSON.parse( cookie );
} catch ( e ) {
this.data = cookie;
return;
}
if ( typeof cookie_obj !== 'object' ) {
this.data = cookie;
return;
}
this.data = cookie_obj.data;
this.expires = parseInt( cookie_obj.expires, 10 );
}
/**
* Check if the cookie data exists.
*/
cookie_storage.prototype.exists = function() {
return typeof this.data !== 'undefined';
};
/**
* Save the cookie data.
*
* @param {mixed} data The cookie data.
*/
cookie_storage.prototype.save = function( data ) {
this.data = data;
get_unix_time_in_seconds = function() {
return Math.round( ( new Date() ).getTime() / 1000 );
}
var remaining_time = this.expires - get_unix_time_in_seconds();
// Check if the cookie is expired.
if ( remaining_time <= 0 ) {
remaining_time = ( this.exdays * 24 * 60 * 60 );
this.expires = get_unix_time_in_seconds() + remaining_time;
}
advads.set_cookie_sec(
this.name,
JSON.stringify( {
expires: this.expires,
data: this.data,
} ),
remaining_time
);
};
advanced_ads_pro_visitor_conditions.cookie_storage = cookie_storage;
// set cookie for referrer visitor condition.
var cookie = new cookie_storage( advanced_ads_pro_visitor_conditions.referrer_cookie_name, advanced_ads_pro_visitor_conditions.referrer_exdays );
if ( ! cookie.exists() && document.referrer !== '' ) {
cookie.save( document.referrer );
}
// Set cookie with page impressions.
var cookie = new cookie_storage( advanced_ads_pro_visitor_conditions.page_impr_cookie_name, advanced_ads_pro_visitor_conditions.page_impr_exdays );
if ( ! cookie.exists() ) {
cookie.save( 1 );
} else {
cookie.save( parseInt( cookie.data, 10 ) + 1 || 1 );
}
} )();

View File

@@ -0,0 +1 @@
!function(){var e;"object"==typeof advanced_ads_pro_visitor_conditions&&(i.prototype.exists=function(){return void 0!==this.data},i.prototype.save=function(e){this.data=e,get_unix_time_in_seconds=function(){return Math.round((new Date).getTime()/1e3)};var i=this.expires-get_unix_time_in_seconds();i<=0&&(i=24*this.exdays*60*60,this.expires=get_unix_time_in_seconds()+i),advads.set_cookie_sec(this.name,JSON.stringify({expires:this.expires,data:this.data}),i)},advanced_ads_pro_visitor_conditions.cookie_storage=i,(e=new i(advanced_ads_pro_visitor_conditions.referrer_cookie_name,advanced_ads_pro_visitor_conditions.referrer_exdays)).exists()||""===document.referrer||e.save(document.referrer),(e=new i(advanced_ads_pro_visitor_conditions.page_impr_cookie_name,advanced_ads_pro_visitor_conditions.page_impr_exdays)).exists()?e.save(parseInt(e.data,10)+1||1):e.save(1));function i(e,i){this.name=e,this.exdays=i,this.data=void 0,this.expires=0;var t=advads.get_cookie(e);if(t){try{var s=JSON.parse(t)}catch(e){return void(this.data=t)}"object"==typeof s?(this.data=s.data,this.expires=parseInt(s.expires,10)):this.data=t}else this.data=t}}();

View File

@@ -0,0 +1,961 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Framework\Utilities\Params;
/**
* Advanced Visitor Conditions module.
*
* -TODO should use a constant for option key as it is shared at multiple positions
*/
class Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions {
protected $options = [];
protected $is_ajax;
// Note: hard-coded in JS
const REFERRER_COOKIE_NAME = 'advanced_ads_pro_visitor_referrer';
// page impression counter
const PAGE_IMPRESSIONS_COOKIE_NAME = 'advanced_ads_page_impressions';
// ad impression cookie name basis
const AD_IMPRESSIONS_COOKIE_NAME = 'advanced_ads_ad_impressions';
public function __construct() {
// load options (and only execute when enabled)
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( isset( $options['advanced-visitor-conditions'] ) ) {
$this->options = $options['advanced-visitor-conditions'];
}
// only execute when enabled
if ( ! isset( $this->options['enabled'] ) || ! $this->options['enabled'] ) {
return ;
}
$is_admin = is_admin();
$this->is_ajax = wp_doing_ajax();
add_filter( 'advanced-ads-visitor-conditions', [ $this, 'visitor_conditions' ] );
// action after ad output is created; used for js injection
add_filter( 'advanced-ads-ad-output', [ $this, 'after_ad_output' ], 10, 2 );
if ( $is_admin ) {
// add referrer check to visitor conditions
// add_action( 'advanced-ads-visitor-conditions-after', array( $this, 'referrer_check_metabox' ), 10, 2 );
/*if ( $this->is_ajax ) {
add_action( 'advanced-ads-ajax-ad-select-init', array( $this, 'ajax_init_ad_select' ) );
}*/
// wp ajax is admin but this will allow other ajax callbacks to avoid setting the referrer
} elseif ( ! $this->is_ajax ) {
// save referrer url in session for visitor referrer url feature
$this->save_first_referrer_url();
// count page impression
$this->count_page_impression();
// register js script to set cookie for cached pages
add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
// enable common frontend logic
// $this->init_common_frontend();
}
}
/**
* Specially prepare for ajax ad select calls.
*
*/
public function ajax_init_ad_select() {
$this->init_common_frontend();
}
/**
* Init for any frontend action (including ajax ad select calls)
*
*/
public function init_common_frontend() {
// check the url referrer condition
// add_filter( 'advanced-ads-can-display', array( $this, 'can_display_by_url_referrer' ), 10, 2 );
}
/**
* Add scripts to non-ajax frontend calls.
*/
public function enqueue_scripts() {
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return;
}
// add dependency to manipulate cookies easily
/*wp_enqueue_script( 'jquery' );
wp_enqueue_script(
'js.cookie',
'//cdnjs.cloudflare.com/ajax/libs/js-cookie/1.5.1/js.cookie.min.js',
array( 'jquery' ),
'1.5.1',
true
);*/
// add own code
wp_register_script(
'advanced_ads_pro/visitor_conditions',
sprintf( '%sinc/conditions%s.js', plugin_dir_url( __FILE__ ), defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' ),
[ ADVADS_SLUG . '-advanced-js' ],
AAP_VERSION
);
// 1 year by default
$referrer_exdays = ( defined( 'ADVANCED_ADS_PRO_REFERRER_EXDAYS' ) && absint( ADVANCED_ADS_PRO_REFERRER_EXDAYS ) > 0 ) ? absint( ADVANCED_ADS_PRO_REFERRER_EXDAYS ) : 365;
// 10 years by default
$page_impressions_exdays = ( defined( 'ADVANCED_ADS_PRO_PAGE_IMPR_EXDAYS' ) && absint( ADVANCED_ADS_PRO_PAGE_IMPR_EXDAYS ) > 0 ) ? absint( ADVANCED_ADS_PRO_PAGE_IMPR_EXDAYS ) : 3650;
wp_localize_script( 'advanced_ads_pro/visitor_conditions', 'advanced_ads_pro_visitor_conditions', [
'referrer_cookie_name' => self::REFERRER_COOKIE_NAME,
'referrer_exdays' => $referrer_exdays,
'page_impr_cookie_name' => self::PAGE_IMPRESSIONS_COOKIE_NAME,
'page_impr_exdays' => $page_impressions_exdays
]);
wp_enqueue_script( 'advanced_ads_pro/visitor_conditions' );
}
/**
* Add visitor conditions
*
* @since 1.0.1
*
* @param array $conditions visitor conditions of the main plugin.
*
* @return array $conditions new global visitor conditions.
*/
public function visitor_conditions( $conditions ) {
$conditions['referrer_url'] = [
'label' => __( 'referrer url', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the referrer URL.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Visitor_Conditions', 'metabox_string' ],
'check' => [ $this, 'check_referrer_url' ],
'helplink' => 'https://wpadvancedads.com/manual/referrer-url/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-referrer-url',
];
$conditions['user_agent'] = [
'label' => __( 'user agent', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the user agent.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Visitor_Conditions', 'metabox_string' ],
'check' => [ $this, 'check_user_agent' ],
'helplink' => 'https://wpadvancedads.com/manual/display-ads-based-on-browser-or-device/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-user-agent',
];
$conditions['capability'] = [
'label' => __( 'user can (capabilities)', 'advanced-ads-pro' ),
'description' => __( ' Display ads based on the users capabilities.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'metabox_capabilities' ],
'check' => [ $this, 'check_capabilities' ],
'passive_info' => [
'hash_fields' => 'value',
'remove' => 'login',
'function' => [ $this, 'get_passive_capability' ],
],
];
$conditions['role'] = [
'label' => __( 'user role', 'advanced-ads-pro' ),
'description' => sprintf(
/* translators: %s is a placeholder for the URL. */
__( 'Display ads based on the user\'s roles. See <a href="%s" target="_blank">List of roles in WordPress</a>.', 'advanced-ads-pro' ),
'https://codex.wordpress.org/Roles_and_Capabilities'
),
'metabox' => [ $this, 'metabox_roles' ],
'check' => [ $this, 'check_roles' ],
'passive_info' => [ 'hash_fields' => 'value', 'remove' => 'login', 'function' => [ 'Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions', 'get_passive_role' ] ],
'helplink' => 'https://wpadvancedads.com/manual/user-role/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-user-role',
];
$conditions['browser_lang'] = [
'label' => __( 'browser language', 'advanced-ads-pro' ),
'description' => __( "Display ads based on the visitor's browser language.", 'advanced-ads-pro' ),
'metabox' => [ $this, 'metabox_browser_lang' ],
'check' => [ $this, 'check_browser_lang' ],
];
$conditions['cookie'] = [
'label' => __( 'cookie', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the value of a cookie. Set the operator to “matches/does not match” and leave the value empty to check only the existence of the cookie.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'metabox_cookie' ],
'check' => [ $this, 'check_cookie' ],
];
$conditions['page_impressions'] = [
'label' => __( 'page impressions', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the number of page impressions the user already made before the current one.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Visitor_Conditions', 'metabox_number' ],
'check' => [ $this, 'check_page_impressions' ],
'helplink' => 'https://wpadvancedads.com/manual/ads-by-page-impressions/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-page-impressions',
];
$conditions['ad_impressions'] = [
'label' => __( 'max. ad impressions', 'advanced-ads-pro' ),
'description' => __( 'Display the ad only for a few impressions in a given period per user.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'metabox_ad_impressions' ],
'check' => [ $this, 'check_ad_impressions' ],
];
$conditions['new_visitor'] = [
'label' => __( 'new visitor', 'advanced-ads-pro' ),
'description' => __( 'Display or hide ads for new visitors.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Visitor_Conditions', 'metabox_is_or_not' ],
'check' => [ $this, 'check_new_visitor' ],
'helplink' => 'https://wpadvancedads.com/manual/ads-for-new-visitors/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-new-visitors',
];
$conditions['adblocker'] = [
'label' => __( 'Adblocker', 'advanced-ads-pro' ),
'description' => __( 'Display or hide ad when user use adblocker.', 'advanced-ads-pro' ),
'metabox' => [ 'Advanced_Ads_Visitor_Conditions', 'metabox_is_or_not' ],
'check' => [ 'Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions', 'check_adblocker' ],
'helplink' => 'https://wpadvancedads.com/manual/ad-blockers/',
];
$conditions['ip_address'] = [
'label' => __( 'User IP Address', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on the user IP address. Enter one IP address per line.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'metabox_ip_address' ],
'check' => [ $this, 'check_ip_address' ],
];
return $conditions;
}
/**
* Save the first referrer url submitted. Cookies is set using JavaScript
*
* @since 1.1.0
*/
protected function save_first_referrer_url() {
$cookie = Params::cookie( self::REFERRER_COOKIE_NAME );
if ( ! $cookie && ! empty( Params::server( 'HTTP_REFERER' ) ) ) {
// make cookies directly available to current request.
$_COOKIE[ self::REFERRER_COOKIE_NAME ] = Params::server( 'HTTP_REFERER' );
}
}
/**
* save page impressions in cookie. Cookies is set using JavaScript
*
* @since 1.1.0
*/
protected function count_page_impression(){
if ( $this->is_ajax ) {
return;
}
// Make cookies directly available to current request.
$impressions = Params::cookie( self::PAGE_IMPRESSIONS_COOKIE_NAME );
$impressions = $impressions ? absint( self::extract_cookie_data( $impressions ) ) : 0;
$_COOKIE[ self::PAGE_IMPRESSIONS_COOKIE_NAME ] = $impressions + 1;
}
/**
* callback to display the "capabilities" condition
*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_capabilities( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$value = isset( $options['value'] ) ? $options['value'] : '';
$operator = isset( $options['operator'] ) ? $options['operator'] : 'can';
// load capabilities
global $wp_roles;
$roles = $wp_roles->roles;
// loop through all roles in order to get registered capabilities
$capabilities = [];
foreach ( $roles as $_role ){
if( isset( $_role['capabilities'] )){
$capabilities += $_role['capabilities'];
}
}
// sort keys by alphabet
ksort( $capabilities );
?><input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<select name="<?php echo $name; ?>[operator]">
<option value="can" <?php selected( 'can', $operator ); ?>><?php _e( 'can', 'advanced-ads-pro' ); ?></option>
<option value="can_not" <?php selected( 'can_not', $operator ); ?>><?php _e( 'can not', 'advanced-ads-pro' ); ?></option>
</select>
<div class="advads-conditions-select-wrap"><select name="<?php echo $name; ?>[value]">
<option><?php _e( '-- choose one --', 'advanced-ads-pro' ); ?></option>
<?php foreach( $capabilities as $cap => $_val ) : ?>
<option value="<?php echo $cap; ?>" <?php selected( $cap, $value ); ?>><?php echo $cap; ?></option>
<?php endforeach; ?>
</select></div>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="https://wpadvancedads.com/manual/display-ads-based-on-user-capabilities/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-user-can" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
<?php
}
/**
* Callback to display the "roles" condition.
*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_roles( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) {
return;
}
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$value = $options['value'] ?? '';
$operator = $options['operator'] ?? 'is';
global $wp_roles;
$roles = $wp_roles->get_names();
?><input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<select name="<?php echo $name; ?>[operator]">
<option value="is" <?php selected( 'is', $operator ); ?>><?php _e( 'is', 'advanced-ads-pro' ); ?></option>
<option value="is_not" <?php selected( 'is_not', $operator ); ?>><?php _e( 'is not', 'advanced-ads-pro' ); ?></option>
</select>
<div class="advads-conditions-select-wrap"><select name="<?php echo $name; ?>[value]">
<option><?php _e( '-- choose one --', 'advanced-ads-pro' ); ?></option>
<?php foreach ( $roles as $_role => $_display_name ) : ?>
<option value="<?php echo $_role; ?>" <?php selected( $_role, $value ); ?>><?php echo $_display_name; ?></option>
<?php endforeach; ?>
</select></div>
<p class="description">
<?php echo $type_options[ $options['type'] ]['description']; ?>
<?php if ( isset( $type_options[ $options['type'] ]['helplink'] ) ) : ?>
<a href="<?php echo esc_url( $type_options[ $options['type'] ]['helplink'] ); ?>" class="advads-manual-link" target="_blank"><?php esc_html_e( 'Manual', 'advanced-ads' ); ?></a>
<?php endif; ?>
</p>
<?php
}
/**
* callback to display the "browser language" condition
*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_browser_lang( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$operator = isset( $options['operator'] ) ? $options['operator'] : 'is';
$value = isset( $options['value'] ) ? $options['value'] : '';
// load browser languages
include plugin_dir_path( __FILE__ ) . 'inc/browser_langs.php';
if( isset( $advads_browser_langs )){
asort( $advads_browser_langs );
}
?><input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<select name="<?php echo $name; ?>[operator]">
<option value="is" <?php selected( 'is', $operator ); ?>><?php _e( 'is', 'advanced-ads-pro' ); ?></option>
<option value="is_not" <?php selected( 'is_not', $operator ); ?>><?php _e( 'is not', 'advanced-ads-pro' ); ?></option>
</select>
<select name="<?php echo $name; ?>[value]">
<option><?php _e( '-- choose one --', 'advanced-ads-pro' ); ?></option>
<?php if( isset( $advads_browser_langs )) :
foreach( $advads_browser_langs as $_key => $_title ) : ?>
<option value="<?php echo $_key; ?>" <?php selected( $_key, $value ); ?>><?php echo $_title; ?></option>
<?php endforeach;
endif; ?>
</select>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="https://wpadvancedads.com/manual/browser-language/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-browser-language" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
<?php
}
/**
* callback to display the "cookie" condition
*
* @param arr $options Options of the condition.
* @param int $index Index of the conditionA.
* @param string $form_name Name of the form, falls back to class constant.
*/
static function metabox_cookie( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
$operator = isset( $options['operator'] ) ? self::maybe_replace_cookie_operator( $options['operator'] ) : 'contain';
// options
$cookie = isset( $options['cookie'] ) ? $options['cookie'] : ''; // Cookie name.
// the value may be slashed if displayed in placement, we also need to convert to htmlentities to display `"`
$value = isset( $options['value'] ) ? htmlentities( wp_unslash( $options['value'] ) ) : '';
ob_start();
if ( 0 <= version_compare( ADVADS_VERSION, '1.9.1' ) ) {
include( ADVADS_ABSPATH . 'admin/views/ad-conditions-string-operators.php' );
}
$operatoroption = ob_get_clean();
$cookieoption = '<input type="text" name="' . $name . '[cookie]" value="' . $cookie . '" placeholder="' . __( 'Cookie Name', 'advanced-ads-pro' ) . '"/>';
$valueoption = '<input type="text" name="' . $name . '[value]" value="' . $value . '" placeholder="' . __( 'Cookie Value', 'advanced-ads-pro' ) . '"/>';
?>
<input type="hidden" name="<?php echo esc_attr( $name ); ?>[type]" value="<?php echo esc_attr( $options['type'] ); ?>"/>
<div class="advads-condition-line-wrap">
<?php
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
/* 1: Cookie Name, 2: Operator, 3: Cookie Value */
printf( '%1$s %2$s %3$s', $cookieoption, $operatoroption, $valueoption );
// phpcs:enable
?>
</div>
<div class="clear"></div>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="https://wpadvancedads.com/manual/cookie-condition/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-cookie" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
<?php
}
/**
* callback to display the condition for ad impressions in a specific time frame
*
* @param arr $options options of the condition
* @param int $index index of the condition
*/
static function metabox_ad_impressions( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = self::get_form_name_with_index( $form_name, $index );
// options
$value = isset( $options['value'] ) ? absint( $options['value'] ) : 0;
$timeout = isset( $options['timeout'] ) ? absint( $options['timeout'] ) : 0;
?><input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<input type="number" required="required" min="0" name="<?php echo $name; ?>[value]" value="<?php echo absint( $value ); ?>"/>
<?php
$impressions_field = '<input type="number" required="required" min="0" name="' . $name . '[timeout]" value="' . $timeout . '"/>';
/* translators: %s is a placeholder for the impressions field */
printf( __( 'within %s seconds', 'advanced-ads-pro' ), $impressions_field ); ?>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="https://wpadvancedads.com/manual/max-ad-impressions/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-max-ad-impressions" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
<?php
}
/**
* IP Address Metabox.
*
* @param array $options options of the condition.
* @param int $index index of the condition.
* @param string $form_name Name of the form, falls back to class constant.
*
* @return void
*/
public static function metabox_ip_address( $options, $index = 0, $form_name = '' ) {
// Early bail!!
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( empty( $options['type'] ) || empty( $type_options[ $options['type'] ] ) ) {
return;
}
$name = self::get_form_name_with_index( $form_name, $index );
$operator = $options['operator'] ?? 'is';
printf( '<input type="hidden" name="%s[type]" value="%s"/>', esc_attr( $name ), esc_attr( $options['type'] ) );
printf(
'<select name="%s[operator]">
<option value="is" %s>%s</option>
<option value="is_not" %s>%s</option>
</select>',
esc_attr( $name ),
selected( 'is', $operator, false ),
esc_html__( 'is', 'advanced-ads-pro' ),
selected( 'is_not', $operator, false ),
esc_html__( 'is not', 'advanced-ads-pro' )
);
printf(
'<textarea name="%s">%s</textarea>',
esc_attr( $name . '[value]' ),
esc_textarea( $options['value'] ?? '' )
);
printf( '<p class="description">%s</p>', esc_html( $type_options[ $options['type'] ]['description'] ) );
}
/**
* check referrer url in frontend
*
* @since 1.0.1
* @param array $options Options of the condition.
* @return bool true if ad can be displayed
*/
static function check_referrer_url( $options = [] ){
// Check if session variable is set.
$cookie = Params::cookie( self::REFERRER_COOKIE_NAME );
if ( ! $cookie ) {
return false;
}
$referrer = self::extract_cookie_data( $cookie );
return Advanced_Ads_Visitor_Conditions::helper_check_string( $referrer, $options );
}
/**
* check user agent in frontend
*
* @since 1.0.1
* @param arr $options options of the condition
* @return bool true if ad can be displayed
*/
static function check_user_agent( $options = [] ){
$user_agent = Params::server( 'HTTP_USER_AGENT', '' );
return Advanced_Ads_Visitor_Conditions::helper_check_string( $user_agent, $options );
}
/**
* check user capabilities in frontend
*
* @since 1.0.1
* @param arr $options options of the condition
* @return bool true if ad can be displayed
*/
static function check_capabilities( $options = [] ){
if ( ! isset( $options['value'] ) || '' === $options['value'] || ! isset( $options['operator'] ) ){
return true;
}
switch ( $options['operator'] ){
case 'can' :
return ( current_user_can( $options['value'] ) );
break;
case 'can_not' :
return ( ! current_user_can( $options['value'] ) );
}
return true;
}
/**
* Check user roles in frontend.
*
* @param arr $options options of the condition
* @return bool true if ad can be displayed
*/
static function check_roles( $options = [] ){
if ( ! isset( $options['value'] ) || '' === $options['value'] || ! isset( $options['operator'] ) ){
return true;
}
$user = wp_get_current_user();
if ( ! is_array( $user->roles ) ) {
return false;
}
switch ( $options['operator'] ) {
case 'is' :
return ( in_array( $options['value'], $user->roles, true ) );
break;
case 'is_not' :
return ! ( in_array( $options['value'], $user->roles, true ) );
}
return true;
}
/**
* check browser language
*
* @since 1.0.1
* @param arr $options options of the condition
* @return bool true if ad can be displayed
*/
static function check_browser_lang( $options = [] ){
if ( ! isset( $options['value'] ) || '' === $options['value'] ){
return true;
}
$language = Params::server( 'HTTP_ACCEPT_LANGUAGE', '' );
if ( '' === $language ) {
return false;
}
// check if the browser lang is within the accepted language string
$regex = "@\b" . $options['value'] . "\b@i"; // \b checks for "whole words"
$result = preg_match( $regex, $language ) === 1;
if ( isset( $options['operator'] ) && $options['operator'] === 'is_not' ) {
return ! $result;
} else {
return $result;
}
}
/**
* check cookie value in frontend
*
* @param array $options Options of the condition.
*
* @return bool true if ad can be displayed
* @since 1.1.1
*/
public static function check_cookie( $options = [] ) {
if ( isset( $options['operator'] ) ) {
$options['operator'] = self::maybe_replace_cookie_operator( $options['operator'] );
}
// do not manipulate the cookie values and the comparison for RegEx.
$encode = ! in_array($options['operator'], ['regex', 'regex_not'], true);
$must_be_set = ! isset( $options['operator'] ) || 'match_not' !== $options['operator'];
// Check if cookie option exists.
if ( empty( $options['cookie'] ) || empty( $options['value'] ) ) {
return $must_be_set;
}
// check if there are cookies.
$cookie = Params::server( 'HTTP_COOKIE', '' );
if ( empty( $cookie ) ) {
return ! $must_be_set;
}
// Get the raw cookie keys and values; the superglobal $_COOKIE holds manipulated keys and values.
$raw_cookies = array_reduce( explode( ';', $cookie ), static function( $carry, $item ) use($encode) {
$cookie_pair = explode( '=', $item, 2 );
if ( count( $cookie_pair ) !== 2 ) {
return $carry;
}
$carry[ trim( $cookie_pair[0] ) ] = $encode
? urlencode( urldecode( wp_unslash( trim( $cookie_pair[1] ) ) ) )
: $cookie_pair[1];
return $carry;
}, [] );
// check if the cookie exists.
if ( ! isset( $raw_cookies[ $options['cookie'] ] ) ) {
return ! $must_be_set;
}
if ($encode) {
// de- and then encode the value, this catches values the user entered decoded and encoded.
$options['value'] = urlencode( urldecode( wp_unslash( $options['value'] ) ) );
}
return Advanced_Ads_Visitor_Conditions::helper_check_string( $raw_cookies[ $options['cookie'] ], $options );
}
/**
* check page_impressions in frontend
*
* @since 1.1.1
* @param array $options Options of the condition.
* @return bool true if ad can be displayed
*/
static function check_page_impressions( $options = [] ){
if ( ! isset( $options['operator'] ) || ! isset( $options['value'] ) ) {
return true;
}
$impressions = Params::cookie( self::PAGE_IMPRESSIONS_COOKIE_NAME, 0 );
if ( ! $impressions ) {
return false;
}
$value = absint( $options['value'] );
$impressions = absint( self::extract_cookie_data( $impressions ) );
switch ( $options['operator'] ){
case 'is_equal' :
if ( $value !== $impressions ) { return false; }
break;
case 'is_higher' :
if ( $value > $impressions ) { return false; }
break;
case 'is_lower' :
if ( $value < $impressions ) { return false; }
break;
}
return true;
}
/**
* Check IP address in frontend.
*
* @param array $options options of the condition.
*
* @return bool true if ad can be displayed.
*/
public static function check_ip_address( $options = [] ) {
if ( empty( $options['value'] ) || empty( $options['operator'] ) ) {
return true;
}
$user_ip = get_user_ip_address();
$ip_addresses = explode( "\n", $options['value'] );
switch ( $options['operator'] ) {
case 'is':
return in_array( $user_ip, $ip_addresses, true );
case 'is_not':
return ! in_array( $user_ip, $ip_addresses, true );
}
return true;
}
/**
* check ad impressions limit for the ad in frontend
*
* @since 1.2.4
* @param arr $options options of the condition
* @param obj $ad Ad
* @return bool true if ad can be displayed
*/
static function check_ad_impressions( $options = [], $ad = false ){
if ( ! is_an_ad( $ad ) || ! isset( $options['value'] ) || ! isset( $options['timeout'] ) ) {
return true;
}
$value = absint( $options['value'] );
$impressions = 0;
$cookie_name = self::AD_IMPRESSIONS_COOKIE_NAME . '_' . $ad->get_id();
$cookie_timeout_name = $cookie_name . '_timeout';
$cookie_value = Params::cookie( $cookie_name );
if ( $cookie_value && Params::cookie( $cookie_timeout_name ) ) {
$impressions = absint( $cookie_value );
if ( $value <= $impressions ) {
return false;
}
}
return true;
}
/**
* check new_visitor in frontend
*
* @since 1.1.1
* @param array $options Options of the condition.
* @return bool true if ad can be displayed
*/
static function check_new_visitor( $options = [] ){
if ( ! isset( $options['operator'] ) ) {
return true;
}
$impressions = Params::cookie( self::PAGE_IMPRESSIONS_COOKIE_NAME, 0 );
if ( $impressions ) {
$impressions = absint( self::extract_cookie_data( $impressions ) );
}
switch ( $options['operator'] ){
case 'is' :
return 1 === $impressions;
break;
case 'is_not' :
return 1 < $impressions;
break;
}
return true;
}
/**
* Get capability information to use in passive cache-busting.
*/
static function get_passive_capability( $options = [] ) {
if ( ! isset( $options['value'] ) ) {
return;
}
$userdata = get_userdata( get_current_user_id() );
if ( ! empty( $userdata->allcaps ) && is_array( $userdata->allcaps ) && ! empty( $userdata->allcaps[ $options['value'] ] ) ) {
return $options['value'];
}
}
/**
* Get role information to use in passive cache-busting.
*/
static function get_passive_role( $options = [] ) {
if ( ! isset( $options['value'] ) ) {
return;
}
$user = wp_get_current_user();
if ( ! empty( $user->roles ) && is_array( $user->roles ) && in_array( $options['value'], $user->roles ) ) {
return $options['value'];
}
}
/**
* Inject ad output and js code.
*
* @since 1.2.4
* @param string $content Ad content.
* @param Ad $ad Ad object.
*
* @return string
*/
public function after_ad_output( $content, Ad $ad ) {
// Do not enqueue on AMP pages.
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return $content;
}
$options = $ad->get_visitor_conditions();
if( is_array( $options )) foreach( $options as $_visitor_condition ){
if( isset( $_visitor_condition['type'] )){
switch( $_visitor_condition['type'] ){
// set limit and timeout for max_ad_impressions visitor condition
case 'ad_impressions' :
$limit = isset( $_visitor_condition['value'] ) ? $_visitor_condition['value'] : '';
$timeout = isset( $_visitor_condition['timeout'] ) ? $_visitor_condition['timeout'] : '';
$timeout = ( $timeout ) ? $timeout : '""';
// cookie names
$cookie_name = self::AD_IMPRESSIONS_COOKIE_NAME . '_' . $ad->get_id();
$cookie_timeout_name = $cookie_name . '_timeout';
// get current count, if timeout not reached yet
$count = ( isset( $_COOKIE[ $cookie_name ] ) && isset( $_COOKIE[ $cookie_timeout_name ] ) ) ? $_COOKIE[ $cookie_name ] : 1;
$content .= '<script>( window.advanced_ads_ready || jQuery( document ).ready ).call( null, function() {'
. 'if( advads.get_cookie( "' . $cookie_timeout_name . '" ) ) {'
. 'if( advads.get_cookie( "' . $cookie_name . '" ) ) {'
. 'var ' . $cookie_name . ' = parseInt( advads.get_cookie( "' . $cookie_name . '" ) ) + 1;'
. '} else { var ' . $cookie_name . ' = 1; }'
. '} else {'
. 'var ' . $cookie_name . ' = 1;'
. 'advads.set_cookie_sec("' . $cookie_timeout_name . '", "true", ' . $timeout . ' );'
. '}'
. 'advads.set_cookie_sec("' . $cookie_name . '", ' . $cookie_name . ', ' . $timeout . ' );';
$content .= '});</script>';
break;
}
}
}
return $content;
}
/**
* Helper function to the name of a form field.
* falls back to default
*
* @param string $form_name form name if submitted.
* @param int $index index of the condition.
*
* @return string
*/
public static function get_form_name_with_index( $form_name = '', $index = 0 ) {
// form name basis
if ( method_exists( 'Advanced_Ads_Visitor_Conditions', 'get_form_name_with_index' ) ) {
return Advanced_Ads_Visitor_Conditions::get_form_name_with_index( $form_name, $index );
} else {
return Advanced_Ads_Visitor_Conditions::FORM_NAME . '[' . $index . ']';
}
}
/**
* Replace operator name to ensure backward compatibility.
*
* @param string $operator Operator name.
* @return string $operator Operator name.
*/
private static function maybe_replace_cookie_operator( $operator ) {
$replace = [
'show' => 'match',
'hide' => 'match_not'
];
return isset( $replace[ $operator ] ) ? $replace[ $operator ] : $operator;
}
/**
* Extract cookie data from a stringified cookie.
*
* @param string $cookie {
* A stringified cookie.
*
* @type string $data Cookie data.
* @type string $expire Expiration time.
* }
* @return mixed The data field on success, original stringified cookie on error.
*/
private static function extract_cookie_data( $cookie ) {
$cookie_array = json_decode( wp_unslash( $cookie ), true );
if (
! is_array( $cookie_array )
|| ! array_key_exists( 'data', $cookie_array )
) {
return $cookie;
}
return $cookie_array['data'];
}
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions;

View File

@@ -0,0 +1,16 @@
<h4><?php _e( 'Display by referrer url', 'advanced-ads-pro' ); ?></h4>
<p><label><input type="checkbox" name="advanced_ad[visitor][referrer-url][enable]" value="1" <?php
checked( $ref_url_enable, 1 ); ?> onclick="advads_toggle_box(this, '#advads-referer-url');"/><?php _e( 'Display ad depending on the external url the user comes from.', 'advanced-ads-pro' ); ?></label></p>
<div id="advads-referer-url"<?php if ( ! $ref_url_enable ) : ?> style="display:none;"<?php endif; ?>>
<?php _e( 'URL', 'advanced-ads-pro' ); ?>
<select name="advanced_ad[visitor][referrer-url][bool]"><?php
foreach ( $referrer_url_bools as $_bool_key => $_bool ) : ?>
<option value="<?php echo $_bool_key; ?>" <?php selected( $_bool_key, $ref_url_bool ); ?>><?php echo $_bool; ?></option>
<?php endforeach; ?></select>
<select name="advanced_ad[visitor][referrer-url][mode]"><?php
foreach ( $referrer_url_modi as $_mod_key => $_modus ) : ?>
<option value="<?php echo $_mod_key; ?>" <?php selected( $_mod_key, $ref_url_mode ); ?>><?php echo $_modus; ?></option>
<?php endforeach; ?></select>
<input type="text" name="advanced_ad[visitor][referrer-url][url]" value="<?php
echo $ref_url_url; ?>" placeholder="<?php _e( 'url of the referrer', 'advanced-ads-pro' ); ?>"/>
</div>

View File

@@ -0,0 +1,6 @@
<?php
$options = Advanced_Ads_Pro::get_instance()->get_options();
$check = isset($options['advanced-visitor-conditions']['enabled']) && $options['advanced-visitor-conditions']['enabled'];
?>
<input name="<?php echo Advanced_Ads_Pro::OPTION_KEY; ?>[advanced-visitor-conditions][enabled]" id="advanced-ads-pro-advanced-visitor-conditions-enabled" type="checkbox" value="1" <?php checked( $check ); ?> />
<label for="advanced-ads-pro-advanced-visitor-conditions-enabled" class="description"><?php _e('Activate module.', 'advanced-ads-pro'); ?></label>

View File

@@ -0,0 +1,107 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Utilities\WordPress;
/**
* Background ads placement module.
*/
class Advanced_Ads_Pro_Module_Background_Ads_Admin {
/**
* Constructor. Register relevant hooks.
*/
public function __construct() {
add_action( 'advanced-ads-placement-options-after-advanced', [ $this, 'placement_options' ], 10, 2 );
add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] );
add_action( 'admin_footer', [ $this, 'inline_script' ] );
}
/**
* Render placement option.
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function placement_options( $placement_slug, $placement ){
if ( $placement->is_type( 'background' ) ) {
$data = $placement->get_data();
$bg_color = ( isset($data['bg_color']) ) ? $data['bg_color'] : '';
$option_content = '<input type="text" value="'. $bg_color .'" class="advads-bg-color-field" name="advads[placements][options][bg_color]"/>';
$description = __( 'Select a background color in case the background image is not high enough to cover the whole screen.', 'advanced-ads-pro' );
WordPress::render_option(
'placement-background-color',
__( 'background', 'advanced-ads-pro' ),
$option_content,
$description );
}
}
/**
* add color picker script to placements page
*
* @since 1.8
*/
function admin_scripts() {
$screen = get_current_screen();
if ( ! function_exists( 'wp_advads' ) || 'edit-advanced_ads_plcmnt' !== $screen->id ) {
return;
};
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script( 'wp-color-picker' );
}
/**
* Add footer script on the placement screen
*
* @since 1.8
*/
public function inline_script() {
if ( ! $this->is_placement_screen() ) {
return;
}
?>
<script>
jQuery( $ => {
for ( const modal of document.getElementsByClassName( 'advads-modal' ) ) {
modal.addEventListener( 'advads-modal-opened', (e) => {
jQuery( e.target ).find( '.advads-bg-color-field' ).wpColorPicker( {
change: ( e, ui ) => {
e.target.value = ui.color.toString();
e.target.dispatchEvent( new Event( 'change' ) );
},
clear: e => {
if ( e.type === 'change' ) {
return;
}
jQuery( e.target ).parent().find( '.advads-bg-color-field' )[0].dispatchEvent( new Event( 'change' ) );
}
} );
} );
}
} );
</script><?php
}
/**
* Whether we are on placement screen
*
* @return bool
*/
private function is_placement_screen() {
static $is_placement_screen;
if ( null === $is_placement_screen ) {
$screen = get_current_screen();
if ( ! $screen ) {
return false;
}
$is_placement_screen = 'edit-advanced_ads_plcmnt' === $screen->id;
}
return $is_placement_screen;
}
}

View File

@@ -0,0 +1,4 @@
<?php
new Advanced_Ads_Pro_Module_Background_Ads_Admin();

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,184 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Tracking\Helpers;
/**
* Background ads class.
*/
class Advanced_Ads_Pro_Module_Background_Ads {
/**
* List of placements.
*
* @var array
*/
private $placements = [];
/**
* Constructor.
*/
public function __construct() {
add_action( 'wp_footer', [ $this, 'footer_injection' ], 20 );
add_action( 'wp_head', [ $this, 'initialise_rotating_click_listener' ] );
// Register output change hook.
add_filter( 'advanced-ads-ad-output', [ $this, 'ad_output' ], 20, 2 );
}
/**
* Creates abort controller to reset the click event listeners for rotating ads.
*
* @return void
*/
public function initialise_rotating_click_listener() {
$this->placements = wp_advads_get_placements();
if ( ! $this->contains_background_placement() ) {
return;
}
wp_register_script( 'advanced-ads-pro/background-ads', '', [], AAP_VERSION, false );
wp_enqueue_script( 'advanced-ads-pro/background-ads' );
wp_add_inline_script( 'advanced-ads-pro/background-ads', 'let abort_controller = new AbortController();' );
}
/**
* Echo the placement output into the footer.
*
* @return void
*/
public function footer_injection() {
foreach ( $this->placements as $placement ) {
if ( $placement->is_type( 'background' ) ) {
the_ad_placement( $placement );
}
}
}
/**
* Check if a background placement exists.
*
* @return bool
*/
protected function contains_background_placement(): bool {
foreach ( $this->placements as $placement ) {
if ( $placement->is_type( 'background' ) ) {
return true;
}
}
return false;
}
/**
* Change ad output.
*
* @param string $output Ad output.
* @param Ad $ad Ad instance.
*
* @return string
*/
public function ad_output( $output, $ad ) {
$parent = $ad->get_parent();
if ( ! $parent || ! $parent->is_type( 'background' ) || ! $ad->is_type( 'image' ) ) {
return $output;
}
$background_color = sanitize_text_field( $parent->get_prop( 'bg_color' ) ) ?: false;
// Get prefix and generate new body class.
$class = wp_advads()->get_frontend_prefix() . 'body-background';
// Get the ad image.
$image = wp_get_attachment_image_src( $ad->get_image_id(), 'full' );
if ( ! $image || empty ( $image[0] ) ) {
return $output;
}
[ $image_url, $image_width, $image_height ] = $image;
$selector = apply_filters( 'advanced-ads-pro-background-selector', 'body' );
$is_amp = function_exists( 'advads_is_amp' ) && advads_is_amp();
/**
* Filter the background placement URL.
*
* @param string $link The URL.
* @param Ad $ad The current ad object.
*/
$link = (string) apply_filters( 'advanced-ads-pro-background-url', $ad->get_url(), $ad );
if ( class_exists( Helpers::class ) ) {
$target = Helpers::get_ad_target( $ad, true );
} else {
$options = Advanced_Ads::get_instance()->options();
$target = isset( $options['target-blank'] ) ? '_blank' : '';
}
$target = '' !== $target ? $target : '_self';
ob_start();
?>
<style>
<?php echo $selector; // phpcs:ignore ?> {
background: url(<?php echo $image_url; // phpcs:ignore ?>) no-repeat fixed;
background-size: 100% auto;
<?php echo $background_color ? "background-color: {$background_color};" : ''; ?>
}
<?php if ( $link && ! $is_amp ) : ?>
<?php
/**
* We should not use links and other tags that should have cursor: pointer as direct children of the $selector.
* That is, we need a nested container (e.g. body > div > a) to make it work correctly.
*/
?>
<?php echo $selector; // phpcs:ignore ?> { cursor: pointer; }
<?php echo $selector; // phpcs:ignore ?> > * { cursor: default; }
<?php endif; ?>
</style>
<?php
/**
* Don't load any javascript on amp.
* Javascript output can be prevented by disabling click tracking and empty url field on ad level.
*/
if ( ! $is_amp ) :
?>
<script>
( window.advanced_ads_ready || document.readyState === 'complete' ).call( null, function () {
// Remove all existing click event listeners and recreate the controller.
abort_controller.abort();
abort_controller = new AbortController();
document.querySelector( '<?php echo esc_attr( $selector ); ?>' ).classList.add( '<?php echo esc_attr( $class ); ?>' );
<?php if ( $link ) : ?>
// Use event delegation because $selector may be not in the DOM yet.
document.addEventListener( 'click', function ( e ) {
if ( e.target.matches( '<?php echo $selector; // phpcs:ignore ?>' ) ) {
<?php
$script = '';
/**
* Add additional script output.
*
* @param string $script The URL.
* @param Ad $ad The current ad object.
*/
$script = (string) apply_filters( 'advanced-ads-pro-background-click-matches-script', $script, $ad );
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- this is our own JS code, escaping will break it
echo $script;
?>
// Open url in new tab.
window.open( '<?php echo esc_url( $link ); ?>', '<?php echo esc_attr( $target ); ?>' );
}
}, { signal: abort_controller.signal } );
<?php endif; ?>
} );
</script>
<?php endif; ?>
<?php
$custom_code = $ad->get_prop( 'custom-code' );
if ( ! empty( $custom_code ) ) {
echo $custom_code;
}
return ob_get_clean();
}
}

View File

@@ -0,0 +1,4 @@
<?php
new Advanced_Ads_Pro_Module_Background_Ads;

View File

@@ -0,0 +1,14 @@
<?php
/**
* Module backend main file
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
// Stop if bbPress isn't activated.
if ( ! class_exists( 'bbPress', false ) ) {
return;
}
( new AdvancedAds\Pro\Modules\bbPress\Admin\Admin() )->hooks();

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,171 @@
<?php
/**
* BBPress module admin
*
* @package AdvancedAds\Pro\Modules\bbPress
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace AdvancedAds\Pro\Modules\bbPress\Admin;
use AdvancedAds\Utilities\WordPress;
use AdvancedAds\Framework\Interfaces\Integration_Interface;
defined( 'ABSPATH' ) || exit;
/**
* Class Admin
*/
class Admin implements Integration_Interface {
/**
* Hook into WordPress.
*
* @return void
*/
public function hooks(): void {
add_action( 'advanced-ads-placement-options-after', [ $this, 'bbpress_comment' ], 10, 2 );
add_action( 'advanced-ads-placement-options-after', [ $this, 'bbpress_static' ], 10, 2 );
}
/**
* Add position setting for static content placement
*
* @param string $slug the placement slug.
* @param Placement $placement the placement.
*
* @return void
*/
public function bbpress_static( $slug, $placement ): void {
// Early bail!!
if ( ! $placement->is_type( 'bbPress static' ) ) {
return;
}
$hooks = $this->get_bbpress_static_hooks();
ob_start();
?>
<label>
<select name="advads[placements][options][bbPress_static_hook]">
<option>---</option>
<?php foreach ( $hooks as $group => $positions ) : ?>
<optgroup label="<?php echo esc_attr( $group ); ?>">
<?php foreach ( $positions as $position ) : ?>
<option value="<?php echo esc_attr( $position ); ?>" <?php selected( $position, $placement->get_prop( 'bbPress_static_hook' ) ); ?>><?php echo esc_html( $position ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
</label>
<?php
$content = ob_get_clean();
WordPress::render_option(
'bbpress-static',
__( 'Position', 'advanced-ads-pro' ),
$content
);
}
/**
* Add position setting for bbpress reply placement
*
* @param string $slug the placement slug.
* @param Placement $placement the placement object.
*
* @return void
*/
public function bbpress_comment( $slug, $placement ): void {
// Early bail!!
if ( ! $placement->is_type( 'bbPress comment' ) ) {
return;
}
$comment_hooks = $this->get_bbpress_comment_hooks();
ob_start();
?>
<label>
<?php esc_html_e( 'Position', 'advanced-ads-pro' ); ?>&nbsp;
<select name="advads[placements][options][bbPress_comment_hook]">
<option>---</option>
<?php foreach ( $comment_hooks as $group => $positions ) : ?>
<optgroup label="<?php echo esc_attr( $group ); ?>">
<?php foreach ( $positions as $position ) : ?>
<option value="<?php echo esc_attr( $position ); ?>"<?php selected( $position, $placement->get_prop( 'bbPress_comment_hook' ) ); ?>><?php echo esc_html( $position ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
</label>
<br>
<br>
<label>
<?php
echo wp_kses(
sprintf(
/* translators: %s: index input field */
__( 'Inject after %s post', 'advanced-ads-pro' ),
sprintf(
'<input type="number" required="required" min="1" step="1" name="advads[placements][options][pro_bbPress_comment_pages_index]" value="%d"/>',
Max( 1, (int) $placement->get_prop( 'pro_bbPress_comment_pages_index' ) )
)
),
[
'input' => [
'type' => [],
'required' => [],
'min' => [],
'step' => [],
'name' => [],
'value' => [],
],
]
);
?>
</label>
<br>
<?php
$content = ob_get_clean();
WordPress::render_option(
'bbpress-comment',
__( 'Position', 'advanced-ads-pro' ),
$content
);
}
/**
* Get bbpress static content hooks
*
* @return array
*/
private function get_bbpress_static_hooks(): array {
return [
__( 'forum topic page', 'advanced-ads-pro' ) => [
'template after replies loop',
'template before replies loop',
],
__( 'single forum page', 'advanced-ads-pro' ) => [
'template after single forum',
'template before single forum',
],
__( 'forums page', 'advanced-ads-pro' ) => [
'template after forums loop',
'template before forums loop',
],
];
}
/**
* Get bbpress comment hooks
*
* @return array
*/
private function get_bbpress_comment_hooks(): array {
return [
__( 'forum topic page', 'advanced-ads-pro' ) => [
'theme after reply content',
'theme before reply content',
'theme after reply author admin details',
],
];
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* Main module class
*
* @package AdvancedAds\Pro\Modules\bbPress
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace AdvancedAds\Pro\Modules\bbPress;
use AdvancedAds\Abstracts\Placement;
use AdvancedAds\Framework\Interfaces\Integration_Interface;
defined( 'ABSPATH' ) || exit;
/**
* Bootstrap the bbPress module
*/
class BBPress implements Integration_Interface {
/**
* Hook into WordPress.
*
* @return void
*/
public function hooks(): void {
add_action( 'init', [ $this, 'init' ], 31 );
}
/**
* Initialize the module.
*
* @return void
*/
public function init(): void {
$placements = wp_advads_get_placements();
foreach ( $placements as $placement ) {
$this->register_hook( $placement, 'bbPress comment', 'bbPress_comment_hook' );
$this->register_hook( $placement, 'bbPress static', 'bbPress_static_hook' );
}
}
/**
* Inject during hooks found in the placement options
*
* @return void
*/
public function execute_hook(): void {
$placements = wp_advads_get_placements();
foreach ( $placements as $id => $placement ) {
if (
( $placement->is_type( 'bbPress comment' ) && $this->is_comment_hook( $placement ) )
|| ( $placement->is_type( 'bbPress static' ) && $this->is_static_content_hook( $placement ) )
) {
the_ad_placement( $id );
}
}
}
/**
* Check if can render static content placement
*
* @param Placement $placement the placement.
*
* @return bool
*/
private function is_static_content_hook( $placement ) {
$hook = current_filter();
$check = str_replace( ' ', '_', 'bbp_' . $placement->get_prop( 'bbPress_static_hook' ) );
return $placement->get_prop( 'bbPress_static_hook' ) && $hook === $check;
}
/**
* Check if can render reply placement
*
* @param Placement $placement the placement.
*
* @return bool
*/
private function is_comment_hook( $placement ) {
$hook = current_filter();
$check = str_replace( ' ', '_', 'bbp_' . $placement->get_prop( 'bbPress_comment_hook' ) );
return $placement->get_prop( 'pro_bbPress_comment_pages_index' )
&& $placement->get_prop( 'bbPress_comment_hook' )
&& $hook === $check
&& (int) $placement->get_prop( 'pro_bbPress_comment_pages_index' ) === did_action( $hook );
}
/**
* Register the bbPress hook.
*
* @param Placement $placement Placement instance.
* @param string $type Type of placement.
* @param string $prop Property name.
*
* @return void
*/
private function register_hook( $placement, $type, $prop ): void {
if ( $placement->is_type( $type ) && $placement->get_prop( $prop ) ) {
$hook = str_replace( ' ', '_', 'bbp_' . $placement->get_prop( $prop ) );
add_action( $hook, [ $this, 'execute_hook' ] );
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Module main file
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
// Stop if bbPress isn't activated.
if ( ! class_exists( 'bbPress', false ) ) {
return;
}
( new AdvancedAds\Pro\Modules\bbPress\BBPress() )->hooks();

View File

@@ -0,0 +1,14 @@
<?php
/**
* Module backend main file
*
* @package AdvancedAds\Pro\Modules\BuddyPress
* @author Advanced Ads <info@wpadvancedads.com>
*/
// Stop if BuddyPress isn't activated.
if ( ! class_exists( 'BuddyPress', false ) ) {
return;
}
( new AdvancedAds\Pro\Modules\BuddyPress\Admin() )->hooks();

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,25 @@
/**
* Renders a html field corresponding to the currently selected field type.
*/
jQuery( document ).on( 'change', '.advads-pro-buddyboss-xprofile-field-type', function() {
const $self = jQuery( this ),
$loading = jQuery( '<span class="advads-loader"></span>' );
// Replace the dynamic field with the loader.
$self.parent().nextAll( '.advanced-ads-buddyboss-xprofile-dynamic-field' ).replaceWith( $loading );
jQuery.ajax( {
type: 'POST',
url: ajaxurl,
data: {
action: 'advads-pro-buddyboss-render-xprofile-field',
field_name: $self.data( 'field-name' ),
field_type: $self.find( 'option:selected' ).data( 'field-type' ),
nonce: advadsglobal.ajax_nonce
},
} ).done( function ( data ) {
$loading.replaceWith( data );
} ).fail( function ( jqXHR, textStatus, errorThrown ) {
$loading.replaceWith( textStatus + ': ' + errorThrown );
} );
} );

View File

@@ -0,0 +1,156 @@
<?php
/**
* BuddyPress module admin
*
* @package AdvancedAds\Pro\Modules\BuddyPress
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace AdvancedAds\Pro\Modules\BuddyPress;
use AdvancedAds\Abstracts\Placement;
use AdvancedAds\Utilities\Conditional;
use AdvancedAds\Framework\Utilities\Params;
use AdvancedAds\Framework\Interfaces\Integration_Interface;
defined( 'ABSPATH' ) || exit;
/**
* Class Admin
*/
class Admin implements Integration_Interface {
/**
* Hook into WordPress.
*
* @return void
*/
public function hooks(): void {
add_action( 'advanced-ads-placement-options-after', [ $this, 'placement_options' ], 10, 2 );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
add_action( 'wp_ajax_advads-pro-buddyboss-render-xprofile-field', [ $this, 'render_xprofile_field_ajax' ] );
}
/**
* Register options for the BuddyPress placement
*
* @param string $slug placement slug.
* @param Placement $placement the placement.
*
* @return void
*/
public function placement_options( $slug, $placement ) {
if ( $placement->is_type( 'buddypress' ) ) {
$placement_options = $placement->get_data();
$buddypress_positions = $this->get_buddypress_hooks();
$current = BuddyPress::get_hook_from_placement_options( $placement );
$activity_type = $placement_options['activity_type'] ?? 'any';
$hook_repeat = ! empty( $placement_options['hook_repeat'] );
$index = ( isset( $placement_options['pro_buddypress_pages_index'] ) ) ? \Advanced_Ads_Pro_Utils::absint( $placement_options['pro_buddypress_pages_index'], 1 ) : 1;
require AA_PRO_ABSPATH . 'modules/buddypress/views/position-option.php';
}
}
/**
* Load the hooks relevant for BuddyPress/BuddyBoss
*
* @return array list of hooks for BuddyPress depending on the BP theme
*/
public function get_buddypress_hooks() {
if ( ! BuddyPress::is_legacy_theme() ) {
return [
__( 'Activity Entry', 'advanced-ads-pro' ) => [
'bp_after_activity_entry' => 'after activity entry',
],
];
}
// Return legacy hooks.
return [
__( 'Activity Entry', 'advanced-ads-pro' ) => [
'bp_before_activity_entry' => 'before activity entry',
'bp_activity_entry_content' => 'activity entry content',
'bp_after_activity_entry' => 'after activity entry',
'bp_before_activity_entry_comments' => 'before activity entry comments',
'bp_activity_entry_comments' => 'activity entry comments',
'bp_after_activity_entry_comments' => 'after activity entry comments',
],
__( 'Group List', 'advanced-ads-pro' ) => [
'bp_directory_groups_item' => 'directory groups item',
],
__( 'Member List', 'advanced-ads-pro' ) => [
'bp_directory_members_item' => 'directory members item',
],
];
}
/**
* Enqueue admin scripts.
*/
public function enqueue_admin_scripts() {
if ( ! Conditional::is_screen_advanced_ads() ) {
return;
}
wp_enqueue_script( 'advanced-ads-pro/buddyboss-admin', plugin_dir_url( __FILE__ ) . 'assets/js/admin.js', [ 'jquery' ], AAP_VERSION, true );
}
/**
* Renders a html field corresponding to the currently selected field type.
*/
public function render_xprofile_field_ajax() {
check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
$field_name = Params::post( 'field_name' );
$field_type = Params::post( 'field_type' );
if (
! Conditional::user_can( 'advanced_ads_edit_ads' )
|| ! $field_name || ! $field_type
) {
die;
}
self::render_xprofile_field(
preg_replace( '/[^a-z0-9\[\]]/', '', $field_name ),
preg_replace( '/[^a-z0-9]/', '', $field_type ),
''
);
die;
}
/**
* Renders a html field corresponding to the currently selected field type.
*
* @param string $name Field name.
* @param string $field_type type Field type.
* @param string $value Field value.
*/
public static function render_xprofile_field( $name, $field_type, $value = '' ) {
if ( function_exists( 'bp_get_active_member_types' ) && BuddyPress::FIELD_MEMBERTYPES === $field_type ) {
$bp_active_member_types = \bp_get_active_member_types();
if ( ! empty( $bp_active_member_types ) ) {
printf(
'<select name="%s[value]" class="advanced-ads-buddyboss-xprofile-dynamic-field">',
esc_attr( $name )
);
foreach ( $bp_active_member_types as $bp_active_member_type ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $bp_active_member_type ),
selected( $bp_active_member_type, (int) $value, false ),
esc_attr( get_post_meta( $bp_active_member_type, '_bp_member_type_label_singular_name', true ) )
);
}
echo '</select>';
}
} else {
printf(
'<input type="text" name="%s[value]" value="%s" class="advanced-ads-buddyboss-xprofile-dynamic-field">',
esc_attr( $name ),
esc_attr( $value )
);
}
}
}

View File

@@ -0,0 +1,481 @@
<?php
/**
* BuddyPress module
*
* @package AdvancedAds\Pro\Modules\BuddyPress
* @author Advanced Ads <info@wpadvancedads.com>
*/
namespace AdvancedAds\Pro\Modules\BuddyPress;
use AdvancedAds\Abstracts\Placement;
use AdvancedAds\Framework\Interfaces\Integration_Interface;
defined( 'ABSPATH' ) || exit;
/**
* Class BuddyPress
*/
class BuddyPress implements Integration_Interface {
/**
* Member Types xprofile field type.
*/
const FIELD_MEMBERTYPES = 'membertypes';
/**
* Textbox xprofile field type.
*/
const FIELD_TEXTBOX = 'textbox';
/**
* Hook into WordPress.
*
* @return void
*/
public function hooks(): void {
add_action( 'init', [ $this, 'init' ], 31 );
add_filter( 'advanced-ads-visitor-conditions', [ $this, 'visitor_conditions' ] );
add_filter( 'advanced-ads-display-conditions', [ $this, 'display_conditions' ] );
}
/**
* Get all placements
*
* @return Placement[]
*/
private function get_all_placements() {
static $placements;
if ( null !== $placements ) {
return $placements;
}
$placements = wp_advads_get_all_placements();
return $placements;
}
/**
* Register relevant hooks
*/
public function init() {
$placements = $this->get_all_placements();
foreach ( $placements as $placement ) {
if ( ! $placement->is_type( 'buddypress' ) ) {
continue;
}
$hook = self::get_hook_from_placement_options( $placement );
add_action( $hook, [ $this, 'execute_hook' ] );
}
}
/**
* Execute frontend hooks
*/
public function execute_hook() {
$placements = $this->get_all_placements();
// Look for the current hook in the placements.
$hook = current_filter();
foreach ( $placements as $id => $placement ) {
$hook_from_option = self::get_hook_from_placement_options( $placement );
if ( ! $placement->is_type( 'buddypress' ) || $hook !== $hook_from_option ) {
continue;
}
$index = Max( 1, (int) $placement->get_prop( 'pro_buddypress_pages_index' ) );
$did_action = did_action( $hook );
if ( $did_action !== $index && ( $placement->get_prop( 'hook_repeat' ) || 0 !== $did_action % $index ) ) {
continue;
}
if ( ! self::is_legacy_theme() && $placement->get_prop( 'activity_type' ) && ! $this->is_activity_type( $placement->get_prop( 'activity_type' ) ) ) {
continue;
}
the_ad_placement( $id );
}
}
/**
* Add visitor condition for BuddyPress profile fields
*
* @param array $conditions visitor conditions of the main plugin.
*
* @return array $conditions new global visitor conditions
*/
public function visitor_conditions( $conditions ) {
// Stop if BuddyPress isn't activated.
if ( ! class_exists( 'BuddyPress', false ) || ! function_exists( 'bp_profile_get_field_groups' ) ) {
return $conditions;
}
$conditions['buddypress_profile_field'] = [
'label' => __( 'BuddyPress profile field', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on BuddyPress profile fields.', 'advanced-ads-pro' ),
'metabox' => [ self::class, 'xprofile_metabox' ],
'check' => [ self::class, 'check_xprofile' ],
'passive_info' => [
'hash_fields' => 'field',
'remove' => 'login',
'function' => [ self::class, 'get_passive' ],
],
];
// Update condition labels when BuddyBoss is used.
if ( self::is_buddyboss() ) {
$conditions['buddypress_profile_field']['label'] = __( 'BuddyBoss profile field', 'advanced-ads-pro' );
$conditions['buddypress_profile_field']['description'] = __( 'Display ads based on BuddyBoss profile fields.', 'advanced-ads-pro' );
}
return $conditions;
}
/**
* Frontend check for the xprofile condition
*
* @param array $options condition options.
*
* @return bool
*/
public static function check_xprofile( $options = [] ) {
if ( ! isset( $options['operator'] ) || ! isset( $options['value'] ) || ! isset( $options['field'] ) ) {
return true;
}
$user_id = get_current_user_id();
$operator = $options['operator'];
$value = trim( $options['value'] );
$field = (int) $options['field'];
if ( ! $user_id ) {
return true;
}
$profile = self::get_profile_field_data( $field, $user_id );
$trimmed_options = [
'operator' => $operator,
'value' => $value,
];
if ( is_array( $profile ) ) {
// Multi fields (checkboxes, dropdowns, etc).
$positive_operator = in_array( $options['operator'], [ 'contain', 'start', 'end', 'match', 'regex' ], true );
if ( ! $profile ) {
return ! $positive_operator;
}
foreach ( $profile as $profile_item ) {
$condition = \Advanced_Ads_Visitor_Conditions::helper_check_string( $profile_item, $trimmed_options );
if (
// If operator is positive, check if at least one string returns `true`.
( $positive_operator && $condition )
// If operator is negative, check if all strings return `true`.
|| ( ! $positive_operator && ! $condition )
) {
break;
}
}
return $condition;
}
// Single fields.
return \Advanced_Ads_Visitor_Conditions::helper_check_string( $profile, $trimmed_options );
}
/**
* Get information to use in passive cache-busting.
*
* @param array $options condition options.
*/
public static function get_passive( $options = [] ) {
if ( ! isset( $options['field'] ) ) {
return;
}
$field = (int) $options['field'];
$user_id = get_current_user_id();
if ( ! $user_id ) {
return;
}
$profile = self::get_profile_field_data( $field, $user_id );
return [
'field' => $options['field'],
'data' => $profile,
];
}
/**
* Get profile field data.
*
* @param int $field Field ID.
* @param int $user_id ID of the user to get field data for.
*
* @return string[]|string
*/
private static function get_profile_field_data( $field, $user_id ) {
if (
! function_exists( 'bp_get_member_type' )
|| ! function_exists( 'bp_get_profile_field_data' )
) {
return [];
}
// Process the "membertypes" field (BuddyBoss).
if (
function_exists( 'bp_get_xprofile_member_type_field_id' )
&& \bp_get_xprofile_member_type_field_id() === $field
&& function_exists( 'bp_member_type_post_by_type' )
) {
$member_types = \bp_get_member_type( $user_id, false );
if ( ! is_array( $member_types ) ) {
return [];
}
return array_map(
function ( $member_type ) {
return \bp_member_type_post_by_type( $member_type );
},
$member_types
);
}
// Process fields other than the "membertypes".
return \bp_get_profile_field_data(
[
'field' => $field,
'user_id' => $user_id,
]
);
}
/**
* Render xprofile visitor condition option
*
* @param array $options condition options.
* @param int $index index of the option.
* @param string $form_name name of the form.
*/
public static function xprofile_metabox( $options, $index = 0, $form_name = '' ) {
if ( ! isset( $options['type'] ) || '' === $options['type'] ) {
return;
}
$type_options = \Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
$groups = \bp_profile_get_field_groups();
$name = \Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions::get_form_name_with_index( $form_name, $index );
$value = isset( $options['value'] ) ? $options['value'] : '';
$field = isset( $options['field'] ) ? (int) $options['field'] : -1;
$value = $options['value'] ?? '';
$operator = $options['operator'] ?? 'is_equal';
$current_field_type = self::get_current_field_type( $field );
require AA_PRO_ABSPATH . 'modules/buddypress/views/xprofile-condition.php';
}
/**
* Get current field type.
*
* @param int $field Field ID.
*
* @return string
*/
private static function get_current_field_type( $field ) {
if ( ! function_exists( 'bp_get_xprofile_member_type_field_id' ) ) {
return self::FIELD_TEXTBOX;
}
if ( \bp_get_xprofile_member_type_field_id() === $field ) {
return self::FIELD_MEMBERTYPES;
}
$groups = \bp_profile_get_field_groups();
return $groups[0]->fields[0]->type ?? self::FIELD_TEXTBOX;
}
/**
* Add display condition for BuddyBoss groups.
*
* @param array $conditions Display conditions of the main plugin.
*
* @return array $conditions New display conditions.
*/
public function display_conditions( $conditions ) {
// Stop if BuddyBoss isn't activated.
if ( ! class_exists( 'BuddyPress', false ) || ! function_exists( 'groups_get_groups' ) ) {
return $conditions;
}
$conditions['buddypress_group'] = [
'label' => __( 'BuddyPress group', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on existing BuddyPress groups.', 'advanced-ads-pro' ),
'metabox' => [ self::class, 'group_metabox' ],
'check' => [ self::class, 'group_check' ],
'options' => [
'global' => false,
],
'helplink' => 'https://wpadvancedads.com/manual/buddyboss-ads/?utm_source=advanced-ads?utm_medium=link&utm_campaign=condition-buddyboss-group',
];
if ( self::is_buddyboss() ) {
$conditions['buddypress_group']['label'] = __( 'BuddyBoss group', 'advanced-ads-pro' );
$conditions['buddypress_group']['description'] = __( 'Display ads based on existing BuddyBoss groups.', 'advanced-ads-pro' );
}
return $conditions;
}
/**
* Callback to display metabox for the BuddyBoss group condition.
*
* @param array $options Options of the condition.
* @param int $index Index of the condition.
* @param string $form_name Name of the form, falls back to class constant.
*/
public static function group_metabox( $options, $index = 0, $form_name = '' ) {
if ( ! isset( $options['type'] ) || '' === $options['type'] ) {
return;
}
$type_options = \Advanced_Ads_Display_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// Get values and select operator based on previous settings.
$operator = ( isset( $options['operator'] ) && 'is_not' === $options['operator'] ) ? 'is_not' : 'is';
$values = ( isset( $options['value'] ) && is_array( $options['value'] ) ) ? array_map( 'absint', $options['value'] ) : [];
// Form name basis.
$name = \Advanced_Ads_Display_Conditions::get_form_name_with_index( $form_name, $index );
$rand = md5( $name );
// Load operator template.
include ADVADS_ABSPATH . 'admin/views/conditions/condition-operator.php';
$groups = self::get_buddypress_group_list();
include AA_PRO_ABSPATH . 'modules/buddypress/views/display-condition-group.php';
include ADVADS_ABSPATH . 'admin/views/conditions/not-selected.php';
?>
<p class="description">
<?php esc_html_e( 'Display ads based on existing BuddyBoss groups.', 'advanced-ads-pro' ); ?>
<a href="https://wpadvancedads.com/manual/buddyboss-ads/?utm_source=advanced-ads?utm_medium=link&utm_campaign=condition-buddyboss-group" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
</div>
<?php
}
/**
* Get the list of BuddyBoss groups.
*
* @return array.
*/
public static function get_buddypress_group_list() {
$list = [];
$groups = \groups_get_groups( [ 'per_page' => -1 ] );
if ( ! isset( $groups['groups'] ) || ! is_array( $groups['groups'] ) ) {
return $list;
}
foreach ( $groups['groups'] as $group ) {
if ( isset( $group->id, $group->name ) ) {
$list[ $group->id ] = $group->name;
}
}
return $list;
}
/**
* Check BuddyBoss group display condition in frontend.
*
* @param array $options options of the condition.
*
* @return bool True if can be displayed.
*/
public static function group_check( $options = [] ) {
if ( ! isset( $options['value'] ) || ! is_array( $options['value'] ) || ! function_exists( 'bp_get_current_group_id' ) ) {
return true;
}
$operator = isset( $options['operator'] ) && 'is_not' === $options['operator'] ? 'is_not' : 'is';
return \Advanced_Ads_Display_Conditions::can_display_ids( \bp_get_current_group_id(), $options['value'], $operator );
}
/**
* Check if we are using BuddyPress legacy theme
*
* @return bool 1 if the site uses the legacy theme
*/
public static function is_legacy_theme() {
return function_exists( 'bp_get_theme_package_id' ) && 'legacy' === \bp_get_theme_package_id();
}
/**
* Return the hook from the selected option
* the legacy method used another format, the new version stores the hooks in the option
*
* @param Placement $placement the placement.
*
* @return string hook name
*/
public static function get_hook_from_placement_options( $placement ) {
$hook = $placement->get_prop( 'buddypress_hook' ) ?? 'bp_after_activity_entry';
if ( empty( $hook ) ) {
return 'bp_after_activity_entry';
}
// This accounts for previous versions of the add-on.
return ( 'bp_' !== substr( $hook, 0, 3 ) )
? str_replace( ' ', '_', 'bp_' . $hook )
: $hook;
}
/**
* Check if BuddyBoss is installed instead of BuddyPress
*
* @return bool true if BuddyBoss is installed and used instead of BuddyPress
*/
public static function is_buddyboss() {
return defined( 'BP_PLATFORM_VERSION' );
}
/**
* Check if passed activity type matches current activity type.
*
* @param string $activity_type Activity type to check.
*
* @return bool
*/
private function is_activity_type( $activity_type ) {
switch ( $activity_type ) {
case 'sitewide':
return ! function_exists( 'bp_is_activity_directory' ) || \bp_is_activity_directory();
case 'group':
return ! function_exists( 'bp_is_group_activity' ) || \bp_is_group_activity();
case 'member':
return ! function_exists( 'bp_is_user_activity' ) || \bp_is_user_activity();
default:
return true;
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Module main file
*
* @package AdvancedAds\Pro\Modules\BuddyPress
* @author Advanced Ads <info@wpadvancedads.com>
*/
// Stop if BuddyPress isn't activated.
if ( ! class_exists( 'BuddyPress', false ) ) {
return;
}
( new AdvancedAds\Pro\Modules\BuddyPress\BuddyPress() )->hooks();

View File

@@ -0,0 +1,26 @@
<?php
/**
* Templace for the Group display condition.
*
* @var array $groups List of BuddyBoss groups.
* @var array $values Selected groups.
* @var string $rand Unique identifier of the current metabox.
* @var string $name Form name attribute.
*/
?>
<input type="hidden" name="<?php echo esc_attr( $name ); ?>[type]" value="<?php echo esc_attr( $options['type'] ); ?>"/>
<div class="advads-conditions-single advads-buttonset">
<?php
foreach ( $groups as $_group_id => $_group_name ) {
$_val = in_array( $_group_id, $values, true ) ? 1 : 0;
$_label = sprintf( '%s (%s)', $_group_name, $_group_id );
$field_id = "advads-conditions-$_group_id-$rand";
?>
<label class="button" for="<?php echo esc_attr( $field_id ); ?>"><?php echo esc_html( $_label ); ?></label><input
type="checkbox"
id="<?php echo esc_attr( $field_id ); ?>"
name="<?php echo esc_attr( $name ); ?>[value][]" <?php checked( $_val, 1 ); ?>
value="<?php echo esc_attr( $_group_id ); ?>">
<?php
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Render position option for the BuddyPress Content placement
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*
* @var string $placement_slug slug of the placement
* @var array $buddypress_positions available positions (hooks)
* @var string $current currently selected position
* @var int $index value of index option
* @var string $activity_type Activity Type
* @var bool $hook_repeat Whether to repeat the hook
*/
use AdvancedAds\Pro\Modules\BuddyPress\BuddyPress;
?>
<div class="advads-option">
<span><?php esc_html_e( 'position', 'advanced-ads-pro' ); ?></span>
<div>
<?php if ( BuddyPress::is_legacy_theme() ) : ?>
<select name="advads[placements][options][buddypress_hook]">
<?php foreach ( $buddypress_positions as $_group => $_positions ) : ?>
<optgroup label="<?php echo esc_html( $_group ); ?>">
<?php foreach ( $_positions as $_position_key => $_position_title ) : ?>
<option value="<?php echo esc_attr( $_position_key ); ?>" <?php selected( $_position_key, $current ); ?>><?php echo esc_html( $_position_title ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
<?php endif; ?>
<p>
<?php
printf(
/* translators: %s is an HTML input element. */
esc_html__( 'Inject after %s. entry', 'advanced-ads-pro' ),
'<input type="number" required="required" min="1" name="advads[placements][options][pro_buddypress_pages_index]" value="' . absint( $index ) . '"/>'
);
?>
</p>
<p>
<label><input type="checkbox" name="advads[placements][options][hook_repeat]" value="1" <?php checked( $hook_repeat ); ?>><?php esc_html_e( 'repeat the position', 'advanced-ads-pro' ); ?></label>
</p>
</div>
</div>
<?php if ( ! BuddyPress::is_legacy_theme() ) : ?>
<div class="advads-option">
<span><?php esc_html_e( 'Stream', 'advanced-ads-pro' ); ?></span>
<div class="advads-buddypress-placement-activity">
<label><input name="advads[placements][options][activity_type]" value="any" type="radio" <?php checked( $activity_type, 'any' ); ?>><?php esc_html_e( 'any', 'advanced-ads-pro' ); ?></label>
<label><input name="advads[placements][options][activity_type]" value="sitewide" type="radio" <?php checked( $activity_type, 'sitewide' ); ?>><?php esc_html_e( 'activity stream', 'advanced-ads-pro' ); ?></label>
<label><input name="advads[placements][options][activity_type]" value="group" type="radio" <?php checked( $activity_type, 'group' ); ?>><?php esc_html_e( 'group feed', 'advanced-ads-pro' ); ?></label>
<label><input name="advads[placements][options][activity_type]" value="member" type="radio" <?php checked( $activity_type, 'member' ); ?>><?php esc_html_e( 'member timeline', 'advanced-ads-pro' ); ?></label>
</div>
</div>
<?php endif; ?>

View File

@@ -0,0 +1,75 @@
<?php
/**
* Render xprofile visitor condition settions.
*
* @package AdvancedAds\Pro\Modules\BuddyPress
* @author Advanced Ads <info@wpadvancedads.com>
*
* @var string $name Base name of the setting.
* @var array $options Condition options.
* @var array $groups BuddyPress field groups.
* @var int $field Field option.
* @var string $value Value option.
* @var array $type_options Options for the condition type.
* @var string $current_field_type Current field type.
*/
use AdvancedAds\Pro\Modules\BuddyPress\BuddyPress;
$manual_link = BuddyPress::is_buddyboss()
? 'https://wpadvancedads.com/manual/buddyboss-ads/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-buddyboss-profile-fields'
: 'https://wpadvancedads.com/ads-on-buddypress-pages/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-buddypress-profile-fields';
?><input type="hidden" name="<?php echo esc_attr( $name ); ?>[type]" value="<?php echo esc_attr( $options['type'] ); ?>"/>
<?php
if ( $groups ) :
?>
<div class="advads-conditions-select-wrap">
<select class="advads-pro-buddyboss-xprofile-field-type" data-field-name="<?php echo esc_attr( $name ); ?>" name="<?php echo esc_attr( $name ); ?>[field]">
<?php
foreach ( $groups as $_group ) :
?>
<optgroup label="<?php echo esc_html( $_group->name ); ?>">
<?php
if ( $_group->fields ) {
foreach ( $_group->fields as $_field ) :
$field_type = self::FIELD_MEMBERTYPES === $_field->type ? self::FIELD_MEMBERTYPES : self::FIELD_TEXTBOX;
?>
<option value="<?php echo esc_attr( $_field->id ); ?>" data-field-type="<?php echo esc_attr( $field_type ); ?>" <?php selected( $field, $_field->id ); ?>>
<?php echo esc_html( $_field->name ); ?>
</option>
<?php
endforeach;
}
?>
</optgroup>
<?php
endforeach;
?>
</select>
</div>
<?php
else :
?>
<p class="advads-notice-inline advads-error">
<?php
/* translators: "profile fields" relates to BuddyPress profile fields */
esc_html_e( 'No profile fields found', 'advanced-ads-pro' );
?>
</p>
<?php
endif;
if ( 0 <= version_compare( ADVADS_VERSION, '1.9.1' ) ) {
include ADVADS_ABSPATH . 'admin/views/ad-conditions-string-operators.php';
}
AdvancedAds\Pro\Modules\BuddyPress\Admin::render_xprofile_field( $name, $current_field_type, $value );
?>
<br class="clear" />
<br />
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="<?php echo esc_url( $manual_link ); ?>" class="advads-manual-link" target="_blank"><?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?></a>
</p>

View File

@@ -0,0 +1,236 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Abstracts\Group;
use AdvancedAds\Utilities\WordPress;
use AdvancedAds\Utilities\Conditional;
use AdvancedAds\Framework\Utilities\Params;
/**
* Cache Busting admin user interface.
*/
class Advanced_Ads_Pro_Module_Cache_Busting_Admin_UI {
/**
* Constructor
*/
public function __construct() {
add_filter( 'advanced-ads-group-hints', [ $this, 'get_group_hints' ], 10, 2 );
if ( empty( Advanced_Ads_Pro::get_instance()->get_options()['cache-busting']['enabled'] ) ) {
return;
}
add_action( 'advanced-ads-placement-options-before-advanced', [ $this, 'admin_placement_options' ], 10, 2 );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
add_action( 'advanced-ads-ad-params-after', [ $this, 'check_ad' ], 9 );
add_filter( 'advanced-ads-ad-notices', [$this, 'ad_notices'], 10, 3 );
add_action( 'wp_ajax_advads-reset-vc-cache', [ $this, 'reset_vc_cache' ] );
add_action( 'wp_ajax_advads-placement-activate-cb', [ $this, 'ads_activate_placement_cb' ] );
}
/**
* Activate placement cache busting
*/
public function ads_activate_placement_cb() {
check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
if ( ! Conditional::user_can( 'advanced_ads_manage_options' ) && ! filter_has_var( INPUT_POST, 'placement' ) ) {
wp_send_json_error( esc_html__( 'You are not allowed to do this.', 'advanced-ads-pro' ), 400 );
}
$placement_slug = sanitize_text_field( Params::post( 'placement' ) );
$placement = wp_advads_get_placement_by_slug( $placement_slug );
if ( $placement ) {
$placement->set_prop( 'cache-busting', Advanced_Ads_Pro_Module_Cache_Busting::OPTION_AUTO );
$placement->save();
wp_send_json_success( esc_html__( 'Cache busting has been successfully enabled for the assigned placement.', 'advanced-ads-pro' ) );
}
wp_send_json_error( esc_html__( "Couldn't find the placement.", 'advanced-ads-pro' ), 400 );
}
/**
* Update visitor consitions cache.
*/
public function reset_vc_cache() {
if ( ! Conditional::user_can( 'advanced_ads_manage_options' ) ) {
return;
}
check_ajax_referer( 'advads-pro-reset-vc-cache-nonce', 'security' );
$time = time();
$options = get_option( 'advanced-ads-pro' );
$options['cache-busting']['vc_cache_reset'] = $time;
update_option( 'advanced-ads-pro', $options );
echo $time;
exit;
}
/**
* add placement options on placement page
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function admin_placement_options( $placement_slug, $placement ) {
$type_options = $placement->get_type_object()->get_options();
if ( isset( $type_options['placement-cache-busting'] ) && ! $type_options['placement-cache-busting'] ) {
return;
}
// l10n
$values = [
Advanced_Ads_Pro_Module_Cache_Busting::OPTION_AUTO => esc_html__( 'auto','advanced-ads-pro' ),
Advanced_Ads_Pro_Module_Cache_Busting::OPTION_ON => esc_html__( 'AJAX','advanced-ads-pro' ),
Advanced_Ads_Pro_Module_Cache_Busting::OPTION_OFF => esc_html__( 'off','advanced-ads-pro' ),
];
// options
$value = $placement->get_prop( 'cache-busting' );
$value = $value === Advanced_Ads_Pro_Module_Cache_Busting::OPTION_ON
? Advanced_Ads_Pro_Module_Cache_Busting::OPTION_ON
: ( $value === Advanced_Ads_Pro_Module_Cache_Busting::OPTION_OFF ? Advanced_Ads_Pro_Module_Cache_Busting::OPTION_OFF : Advanced_Ads_Pro_Module_Cache_Busting::OPTION_AUTO );
ob_start();
foreach ( $values as $k => $l ) {
$selected = checked( $value, $k, false );
echo '<label><input' . $selected . ' type="radio" name="advads[placements][options][cache-busting]" value="'.$k.'" id="advads-placement-'.
$placement_slug.'-cache-busting-'.$k.'"/>'.$l.'</label>';
}
$option_content = ob_get_clean();
WordPress::render_option(
'placement-cache-busting',
_x( 'Cache-busting', 'placement admin label', 'advanced-ads-pro' ),
$option_content );
}
/**
* enqueue scripts for validation the ad
*/
public function enqueue_admin_scripts() {
$screen = get_current_screen();
$uriRelPath = plugin_dir_url( __FILE__ );
if ( isset( $screen->id ) && $screen->id === 'advanced_ads' ) { //ad edit page
wp_register_script( 'krux/prescribe', $uriRelPath . 'inc/prescribe.js', [ 'jquery' ], '1.1.3' );
wp_enqueue_script( 'advanced-ads-pro/cache-busting-admin', $uriRelPath . 'inc/admin.js', [ 'krux/prescribe' ], AAP_VERSION );
} elseif ( Conditional::is_screen_advanced_ads() ) {
wp_enqueue_script( 'advanced-ads-pro/cache-busting-admin', $uriRelPath . 'inc/admin.js', [], AAP_VERSION );
}
}
/**
* add validation for cache-busting
*
* @param Ad $ad Ad instance.
*/
public function check_ad( $ad ) {
include dirname( __FILE__ ) . '/views/settings_check_ad.php';
}
/**
* show cache-busting specific ad notices
*
* @since 1.13.1
*
* @param array $notices Notices.
* @param array $box Current meta box.
* @param Ad $ad Ad instance.
*
* @return array
*/
public function ad_notices( $notices, $box, $ad ): array {
// Show hint that for ad-group ad type, cache-busting method will only be AJAX or off
if ( 'ad-parameters-box' === $box['id'] && $ad->is_type( 'group' ) ) {
$notices[] = [
'text' => __( 'The <em>Ad Group</em> ad type can only use AJAX or no cache-busting, but not passive cache-busting.', 'advanced-ads-pro' ),
// 'class' => 'advads-ad-notice-pro-ad-group-cache-busting',
];
}
return $notices;
}
/**
* Get group hints.
*
* @param string[] $hints Group hints (escaped strings).
* @param Group $group The group object.
*
* @return string[]
*/
public function get_group_hints( $hints, Group $group ) {
// Pro is installed but cache busting is disabled.
if ( empty( Advanced_Ads_Pro::get_instance()->get_options()['cache-busting']['enabled'] ) ) {
$hints[] = sprintf(
wp_kses(
/* translators: %s is a URL. */
__( 'It seems that a caching plugin is activated. Your ads might not rotate properly while cache busting is disabled. <a href="%s" target="_blank">Activate cache busting.</a>', 'advanced-ads-pro' ),
[
'a' => [
'href' => [],
'target' => [],
],
]
),
esc_url( admin_url( 'admin.php?page=advanced-ads-settings#top#pro' ) )
);
return $hints;
}
$placements = wp_advads_placements_by_item_id( 'group_' . $group->get_id() );
// The group doesn't use a placement.
if (
! $placements
&& empty( Advanced_Ads_Pro::get_instance()->get_options()['cache-busting']['passive_all'] )
) {
$hints[] = sprintf(
wp_kses(
/* translators: %s is a URL. */
__( 'You need a placement to deliver this group using cache busting. <a href="%s" target="_blank">Create a placement now.</a>', 'advanced-ads-pro' ),
[
'a' => [
'href' => [],
'target' => [],
],
]
),
esc_url( admin_url( 'admin.php?page=advanced-ads-placements' ) )
);
return $hints;
}
// The Group uses a placement where cache busting is disabled.
foreach ( $placements as $slug => $placement ) {
$placement_data = $placement->get_data();
if ( isset( $placement_data['cache-busting'] )
&& $placement_data['cache-busting'] === Advanced_Ads_Pro_Module_Cache_Busting::OPTION_OFF
) {
$hints[] = sprintf(
wp_kses(
/* translators: %s is a URL. */
__( 'It seems that a caching plugin is activated. Your ads might not rotate properly, while cache busting is disabled for the placement your group is using. <a href="#" data-placement="%s" class="js-placement-activate-cb">Activate cache busting for this placement.</a>', 'advanced-ads-pro' ),
[
'a' => [
'href' => [],
'data-placement' => [],
'class' => [],
],
]
),
$slug
);
return $hints;
}
}
return $hints;
}
}

View File

@@ -0,0 +1,44 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin class for the Advanced Ads Pro Cache Busting module.
*
* @package AdvancedAds\Pro\Modules\Cache_Busting
* @author Advanced Ads <info@wpadvancedads.com>
*/
/**
* Class Cache_Busting
*/
class Advanced_Ads_Pro_Module_Cache_Busting_Admin {
/**
* The constructor
*/
public function __construct() {
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ] );
}
/**
* Add settings for the module.
*
* @return void
*/
public function settings_init(): void {
add_settings_field(
'module-cache-busting',
__( 'Cache Busting', 'advanced-ads-pro' ),
[ $this, 'render_settings' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings',
Advanced_Ads_Pro::OPTION_KEY . '_modules-enable'
);
}
/**
* Render the settings.
*
* @return void
*/
public function render_settings() {
include_once __DIR__ . '/views/settings.php';
}
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Cache_Busting_Admin;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
<?php
// module configuration
$path = dirname( __FILE__ );
return [
'classmap' => [
'Advanced_Ads_Pro_Cache_Busting_Server_Info' => $path . '/server-info.class.php',
],
'textdomain' => null,
];

View File

@@ -0,0 +1,120 @@
jQuery( document ).ready(function( $ ){
$( '.advads-option-group-refresh input:checkbox:checked' ).each( function() {
var number_option = $( this ).parents( '.advads-ad-group-form' ).find( '.advads-option-group-number' );
number_option.val( 'all' ).hide();
});
$( '.advads-option-group-refresh input:checkbox' ).on( 'click', function() {
var number_option = $( this ).parents( '.advads-ad-group-form' ).find( '.advads-option-group-number' );
if ( this.checked ) {
number_option.val( 'all' ).hide();
} else {
number_option.show();
}
});
jQuery( '.advads-option-placement-cache-busting input' ).on( 'change', function() {
var cb_state = jQuery( this ).val(),
$inputs = jQuery( this ).closest( '.advads-placements-table-options' ).find( '.advanced-ads-inputs-dependent-on-cb' );
if ( 'off' === cb_state ) {
// Hide UI elements that work only with cache-busting.
$inputs.hide().next().show();
}
else {
$inputs.show().next().hide();
}
});
$( '#advads-pro-vc-hash-change' ).on( 'click', function() {
var $button = $(this);
var $ok = jQuery( '#advads-pro-vc-hash-change-ok' );
var $error = jQuery( '#advads-pro-vc-hash-change-error' );
$( '<span class="spinner advads-spinner"></span>' ).insertAfter( $button );
$button.hide();
$ok.hide();
$error.hide();
jQuery.ajax( {
type: 'POST',
url: ajaxurl,
data: {
action: 'advads-reset-vc-cache',
security: $('#advads-pro-reset-vc-cache-nonce').val()
},
} ).done(function( data ) {
jQuery( '#advads-pro-vc-hash' ).val( data );
$ok.show();
} ).fail(function( jqXHR, textStatus ) {
$error.show();
} ).always( function() {
$( 'span.spinner' ).remove();
$button.show();
} );
});
$('.js-placement-activate-cb').on('click',function( e ){
e.preventDefault();
const button = $( this );
const placement = button.data('placement');
const loader = jQuery( '<span class="advads-loader"></span>' );
// Replace the dynamic field with the loader.
button.parent().replaceWith( loader );
$.post( ajaxurl, {
action: 'advads-placement-activate-cb',
placement: placement.toString(),
nonce: window.advads_geo_translation.nonce
} )
.done( function ( result ) {
if ( ! $.isPlainObject( result ) ) {
return;
}
loader.replaceWith( '<p class="advads-notice-inline advads-idea">' + result.data + '</p>' );
} )
.fail( function ( jqXHR, errormessage, errorThrown ) {
loader.replaceWith( '<p class="advads-notice-inline advads-error">' + jqXHR.responseJSON.data + '</p>' );
} )
});
});
function advads_cb_check_set_status( status, msg ) {
if ( status === true ) {
jQuery( '#advads-cache-busting-possibility' ).val( true );
} else {
jQuery( '#advads-cache-busting-possibility' ).val( false );
jQuery( '#advads-cache-busting-error-result' ).append( msg ? '<br />' + msg : '' ).show();
}
}
function advads_cb_check_ad_markup( ad_content ) {
if ( ! ad_content ) {
return;
}
// checks whether the ad contains the jQuery.document.ready() and document.write(ln) functions
if ( ( /\)\.ready\(/.test( ad_content ) || /(\$|jQuery)\(\s*?function\(\)/.test( ad_content ) ) && /document\.write/.test( ad_content ) ) {
advads_cb_check_set_status( false );
return;
}
var search_str = 'cache_busting_test';
var source = ad_content += search_str;
var parser = new Prescribe( source, { autoFix: true } );
var tok, result = '';
while ( ( tok = parser.readToken() ) ) {
if (tok) {
result += Prescribe.tokenToString(tok);
}
}
advads_cb_check_set_status( ( result.substr( - search_str.length ) === search_str ) ? true : false );
}

View File

@@ -0,0 +1,924 @@
/**
* @file prescribe
* @description Tiny, forgiving HTML parser
* @version v1.1.3
* @see {@link https://github.com/krux/prescribe/}
* @license MIT
* @author Derek Brans
* @copyright 2017 Krux Digital, Inc
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["Prescribe"] = factory();
else
root["Prescribe"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _HtmlParser = __webpack_require__(1);
var _HtmlParser2 = _interopRequireDefault(_HtmlParser);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
module.exports = _HtmlParser2['default'];
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
var _supports = __webpack_require__(2);
var supports = _interopRequireWildcard(_supports);
var _streamReaders = __webpack_require__(3);
var streamReaders = _interopRequireWildcard(_streamReaders);
var _fixedReadTokenFactory = __webpack_require__(6);
var _fixedReadTokenFactory2 = _interopRequireDefault(_fixedReadTokenFactory);
var _utils = __webpack_require__(5);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Detection regular expressions.
*
* Order of detection matters: detection of one can only
* succeed if detection of previous didn't
* @type {Object}
*/
var detect = {
comment: /^<!--/,
endTag: /^<\//,
atomicTag: /^<\s*(script|style|noscript|iframe|textarea)[\s\/>]/i,
startTag: /^</,
chars: /^[^<]/
};
/**
* HtmlParser provides the capability to parse HTML and return tokens
* representing the tags and content.
*/
var HtmlParser = function () {
/**
* Constructor.
*
* @param {string} stream The initial parse stream contents.
* @param {Object} options The options
* @param {boolean} options.autoFix Set to true to automatically fix errors
*/
function HtmlParser() {
var _this = this;
var stream = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, HtmlParser);
this.stream = stream;
var fix = false;
var fixedTokenOptions = {};
for (var key in supports) {
if (supports.hasOwnProperty(key)) {
if (options.autoFix) {
fixedTokenOptions[key + 'Fix'] = true; // !supports[key];
}
fix = fix || fixedTokenOptions[key + 'Fix'];
}
}
if (fix) {
this._readToken = (0, _fixedReadTokenFactory2['default'])(this, fixedTokenOptions, function () {
return _this._readTokenImpl();
});
this._peekToken = (0, _fixedReadTokenFactory2['default'])(this, fixedTokenOptions, function () {
return _this._peekTokenImpl();
});
} else {
this._readToken = this._readTokenImpl;
this._peekToken = this._peekTokenImpl;
}
}
/**
* Appends the given string to the parse stream.
*
* @param {string} str The string to append
*/
HtmlParser.prototype.append = function append(str) {
this.stream += str;
};
/**
* Prepends the given string to the parse stream.
*
* @param {string} str The string to prepend
*/
HtmlParser.prototype.prepend = function prepend(str) {
this.stream = str + this.stream;
};
/**
* The implementation of the token reading.
*
* @private
* @returns {?Token}
*/
HtmlParser.prototype._readTokenImpl = function _readTokenImpl() {
var token = this._peekTokenImpl();
if (token) {
this.stream = this.stream.slice(token.length);
return token;
}
};
/**
* The implementation of token peeking.
*
* @returns {?Token}
*/
HtmlParser.prototype._peekTokenImpl = function _peekTokenImpl() {
for (var type in detect) {
if (detect.hasOwnProperty(type)) {
if (detect[type].test(this.stream)) {
var token = streamReaders[type](this.stream);
if (token) {
if (token.type === 'startTag' && /script|style/i.test(token.tagName)) {
return null;
} else {
token.text = this.stream.substr(0, token.length);
return token;
}
}
}
}
}
};
/**
* The public token peeking interface. Delegates to the basic token peeking
* or a version that performs fixups depending on the `autoFix` setting in
* options.
*
* @returns {object}
*/
HtmlParser.prototype.peekToken = function peekToken() {
return this._peekToken();
};
/**
* The public token reading interface. Delegates to the basic token reading
* or a version that performs fixups depending on the `autoFix` setting in
* options.
*
* @returns {object}
*/
HtmlParser.prototype.readToken = function readToken() {
return this._readToken();
};
/**
* Read tokens and hand to the given handlers.
*
* @param {Object} handlers The handlers to use for the different tokens.
*/
HtmlParser.prototype.readTokens = function readTokens(handlers) {
var tok = void 0;
while (tok = this.readToken()) {
// continue until we get an explicit "false" return
if (handlers[tok.type] && handlers[tok.type](tok) === false) {
return;
}
}
};
/**
* Clears the parse stream.
*
* @returns {string} The contents of the parse stream before clearing.
*/
HtmlParser.prototype.clear = function clear() {
var rest = this.stream;
this.stream = '';
return rest;
};
/**
* Returns the rest of the parse stream.
*
* @returns {string} The contents of the parse stream.
*/
HtmlParser.prototype.rest = function rest() {
return this.stream;
};
return HtmlParser;
}();
exports['default'] = HtmlParser;
HtmlParser.tokenToString = function (tok) {
return tok.toString();
};
HtmlParser.escapeAttributes = function (attrs) {
var escapedAttrs = {};
for (var name in attrs) {
if (attrs.hasOwnProperty(name)) {
escapedAttrs[name] = (0, _utils.escapeQuotes)(attrs[name], null);
}
}
return escapedAttrs;
};
HtmlParser.supports = supports;
for (var key in supports) {
if (supports.hasOwnProperty(key)) {
HtmlParser.browserHasFlaw = HtmlParser.browserHasFlaw || !supports[key] && key;
}
}
/***/ },
/* 2 */
/***/ function(module, exports) {
'use strict';
exports.__esModule = true;
var tagSoup = false;
var selfClose = false;
var work = window.document.createElement('div');
try {
var html = '<P><I></P></I>';
work.innerHTML = html;
exports.tagSoup = tagSoup = work.innerHTML !== html;
} catch (e) {
exports.tagSoup = tagSoup = false;
}
try {
work.innerHTML = '<P><i><P></P></i></P>';
exports.selfClose = selfClose = work.childNodes.length === 2;
} catch (e) {
exports.selfClose = selfClose = false;
}
work = null;
exports.tagSoup = tagSoup;
exports.selfClose = selfClose;
/***/ },
/* 3 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
exports.comment = comment;
exports.chars = chars;
exports.startTag = startTag;
exports.atomicTag = atomicTag;
exports.endTag = endTag;
var _tokens = __webpack_require__(4);
/**
* Regular Expressions for parsing tags and attributes
*
* @type {Object}
*/
var REGEXES = {
startTag: /^<([\-A-Za-z0-9_!:]+)((?:\s+[\w\-]+(?:\s*=?\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
endTag: /^<\/([\-A-Za-z0-9_:]+)[^>]*>/,
attr: /(?:([\-A-Za-z0-9_]+)\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))|(?:([\-A-Za-z0-9_]+)(\s|$)+)/g,
fillAttr: /^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noresize|noshade|nowrap|readonly|selected)$/i
};
/**
* Reads a comment token
*
* @param {string} stream The input stream
* @returns {CommentToken}
*/
function comment(stream) {
var index = stream.indexOf('-->');
if (index >= 0) {
return new _tokens.CommentToken(stream.substr(4, index - 1), index + 3);
}
}
/**
* Reads non-tag characters.
*
* @param {string} stream The input stream
* @returns {CharsToken}
*/
function chars(stream) {
var index = stream.indexOf('<');
return new _tokens.CharsToken(index >= 0 ? index : stream.length);
}
/**
* Reads start tag token.
*
* @param {string} stream The input stream
* @returns {StartTagToken}
*/
function startTag(stream) {
var endTagIndex = stream.indexOf('>');
if (endTagIndex !== -1) {
var match = stream.match(REGEXES.startTag);
if (match) {
var attrs = {};
var booleanAttrs = {};
var rest = match[2];
match[2].replace(REGEXES.attr, function (match, name) {
if (!(arguments[2] || arguments[3] || arguments[4] || arguments[5])) {
attrs[name] = '';
} else if (arguments[5]) {
attrs[arguments[5]] = '';
booleanAttrs[arguments[5]] = true;
} else {
attrs[name] = arguments[2] || arguments[3] || arguments[4] || REGEXES.fillAttr.test(name) && name || '';
}
rest = rest.replace(match, '');
});
return new _tokens.StartTagToken(match[1], match[0].length, attrs, booleanAttrs, !!match[3], rest.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''));
}
}
}
/**
* Reads atomic tag token.
*
* @param {string} stream The input stream
* @returns {AtomicTagToken}
*/
function atomicTag(stream) {
var start = startTag(stream);
if (start) {
var rest = stream.slice(start.length);
// for optimization, we check first just for the end tag
if (rest.match(new RegExp('<\/\\s*' + start.tagName + '\\s*>', 'i'))) {
// capturing the content is inefficient, so we do it inside the if
var match = rest.match(new RegExp('([\\s\\S]*?)<\/\\s*' + start.tagName + '\\s*>', 'i'));
if (match) {
return new _tokens.AtomicTagToken(start.tagName, match[0].length + start.length, start.attrs, start.booleanAttrs, match[1]);
}
}
}
}
/**
* Reads an end tag token.
*
* @param {string} stream The input stream
* @returns {EndTagToken}
*/
function endTag(stream) {
var match = stream.match(REGEXES.endTag);
if (match) {
return new _tokens.EndTagToken(match[1], match[0].length);
}
}
/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
exports.EndTagToken = exports.AtomicTagToken = exports.StartTagToken = exports.TagToken = exports.CharsToken = exports.CommentToken = exports.Token = undefined;
var _utils = __webpack_require__(5);
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Token is a base class for all token types parsed. Note we don't actually
* use intheritance due to IE8's non-existent ES5 support.
*/
var Token =
/**
* Constructor.
*
* @param {string} type The type of the Token.
* @param {Number} length The length of the Token text.
*/
exports.Token = function Token(type, length) {
_classCallCheck(this, Token);
this.type = type;
this.length = length;
this.text = '';
};
/**
* CommentToken represents comment tags.
*/
var CommentToken = exports.CommentToken = function () {
/**
* Constructor.
*
* @param {string} content The content of the comment
* @param {Number} length The length of the Token text.
*/
function CommentToken(content, length) {
_classCallCheck(this, CommentToken);
this.type = 'comment';
this.length = length || (content ? content.length : 0);
this.text = '';
this.content = content;
}
CommentToken.prototype.toString = function toString() {
return '<!--' + this.content;
};
return CommentToken;
}();
/**
* CharsToken represents non-tag characters.
*/
var CharsToken = exports.CharsToken = function () {
/**
* Constructor.
*
* @param {Number} length The length of the Token text.
*/
function CharsToken(length) {
_classCallCheck(this, CharsToken);
this.type = 'chars';
this.length = length;
this.text = '';
}
CharsToken.prototype.toString = function toString() {
return this.text;
};
return CharsToken;
}();
/**
* TagToken is a base class for all tag-based Tokens.
*/
var TagToken = exports.TagToken = function () {
/**
* Constructor.
*
* @param {string} type The type of the token.
* @param {string} tagName The tag name.
* @param {Number} length The length of the Token text.
* @param {Object} attrs The dictionary of attributes and values
* @param {Object} booleanAttrs If an entry has 'true' then the attribute
* is a boolean attribute
*/
function TagToken(type, tagName, length, attrs, booleanAttrs) {
_classCallCheck(this, TagToken);
this.type = type;
this.length = length;
this.text = '';
this.tagName = tagName;
this.attrs = attrs;
this.booleanAttrs = booleanAttrs;
this.unary = false;
this.html5Unary = false;
}
/**
* Formats the given token tag.
*
* @param {TagToken} tok The TagToken to format.
* @param {?string} [content=null] The content of the token.
* @returns {string} The formatted tag.
*/
TagToken.formatTag = function formatTag(tok) {
var content = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var str = '<' + tok.tagName;
for (var key in tok.attrs) {
if (tok.attrs.hasOwnProperty(key)) {
str += ' ' + key;
var val = tok.attrs[key];
if (typeof tok.booleanAttrs === 'undefined' || typeof tok.booleanAttrs[key] === 'undefined') {
str += '="' + (0, _utils.escapeQuotes)(val) + '"';
}
}
}
if (tok.rest) {
str += ' ' + tok.rest;
}
if (tok.unary && !tok.html5Unary) {
str += '/>';
} else {
str += '>';
}
if (content !== undefined && content !== null) {
str += content + '</' + tok.tagName + '>';
}
return str;
};
return TagToken;
}();
/**
* StartTagToken represents a start token.
*/
var StartTagToken = exports.StartTagToken = function () {
/**
* Constructor.
*
* @param {string} tagName The tag name.
* @param {Number} length The length of the Token text
* @param {Object} attrs The dictionary of attributes and values
* @param {Object} booleanAttrs If an entry has 'true' then the attribute
* is a boolean attribute
* @param {boolean} unary True if the tag is a unary tag
* @param {string} rest The rest of the content.
*/
function StartTagToken(tagName, length, attrs, booleanAttrs, unary, rest) {
_classCallCheck(this, StartTagToken);
this.type = 'startTag';
this.length = length;
this.text = '';
this.tagName = tagName;
this.attrs = attrs;
this.booleanAttrs = booleanAttrs;
this.html5Unary = false;
this.unary = unary;
this.rest = rest;
}
StartTagToken.prototype.toString = function toString() {
return TagToken.formatTag(this);
};
return StartTagToken;
}();
/**
* AtomicTagToken represents an atomic tag.
*/
var AtomicTagToken = exports.AtomicTagToken = function () {
/**
* Constructor.
*
* @param {string} tagName The name of the tag.
* @param {Number} length The length of the tag text.
* @param {Object} attrs The attributes.
* @param {Object} booleanAttrs If an entry has 'true' then the attribute
* is a boolean attribute
* @param {string} content The content of the tag.
*/
function AtomicTagToken(tagName, length, attrs, booleanAttrs, content) {
_classCallCheck(this, AtomicTagToken);
this.type = 'atomicTag';
this.length = length;
this.text = '';
this.tagName = tagName;
this.attrs = attrs;
this.booleanAttrs = booleanAttrs;
this.unary = false;
this.html5Unary = false;
this.content = content;
}
AtomicTagToken.prototype.toString = function toString() {
return TagToken.formatTag(this, this.content);
};
return AtomicTagToken;
}();
/**
* EndTagToken represents an end tag.
*/
var EndTagToken = exports.EndTagToken = function () {
/**
* Constructor.
*
* @param {string} tagName The name of the tag.
* @param {Number} length The length of the tag text.
*/
function EndTagToken(tagName, length) {
_classCallCheck(this, EndTagToken);
this.type = 'endTag';
this.length = length;
this.text = '';
this.tagName = tagName;
}
EndTagToken.prototype.toString = function toString() {
return '</' + this.tagName + '>';
};
return EndTagToken;
}();
/***/ },
/* 5 */
/***/ function(module, exports) {
'use strict';
exports.__esModule = true;
exports.escapeQuotes = escapeQuotes;
/**
* Escape quotes in the given value.
*
* @param {string} value The value to escape.
* @param {string} [defaultValue=''] The default value to return if value is falsy.
* @returns {string}
*/
function escapeQuotes(value) {
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
// There's no lookback in JS, so /(^|[^\\])"/ only matches the first of two `"`s.
// Instead, just match anything before a double-quote and escape if it's not already escaped.
return !value ? defaultValue : value.replace(/([^"]*)"/g, function (_, prefix) {
return (/\\/.test(prefix) ? prefix + '"' : prefix + '\\"'
);
});
}
/***/ },
/* 6 */
/***/ function(module, exports) {
'use strict';
exports.__esModule = true;
exports['default'] = fixedReadTokenFactory;
/**
* Empty Elements - HTML 4.01
*
* @type {RegExp}
*/
var EMPTY = /^(AREA|BASE|BASEFONT|BR|COL|FRAME|HR|IMG|INPUT|ISINDEX|LINK|META|PARAM|EMBED)$/i;
/**
* Elements that you can intentionally leave open (and which close themselves)
*
* @type {RegExp}
*/
var CLOSESELF = /^(COLGROUP|DD|DT|LI|OPTIONS|P|TD|TFOOT|TH|THEAD|TR)$/i;
/**
* Corrects a token.
*
* @param {Token} tok The token to correct
* @returns {Token} The corrected token
*/
function correct(tok) {
if (tok && tok.type === 'startTag') {
tok.unary = EMPTY.test(tok.tagName) || tok.unary;
tok.html5Unary = !/\/>$/.test(tok.text);
}
return tok;
}
/**
* Peeks at the next token in the parser.
*
* @param {HtmlParser} parser The parser
* @param {Function} readTokenImpl The underlying readToken implementation
* @returns {Token} The next token
*/
function peekToken(parser, readTokenImpl) {
var tmp = parser.stream;
var tok = correct(readTokenImpl());
parser.stream = tmp;
return tok;
}
/**
* Closes the last token.
*
* @param {HtmlParser} parser The parser
* @param {Array<Token>} stack The stack
*/
function closeLast(parser, stack) {
var tok = stack.pop();
// prepend close tag to stream.
parser.prepend('</' + tok.tagName + '>');
}
/**
* Create a new token stack.
*
* @returns {Array<Token>}
*/
function newStack() {
var stack = [];
stack.last = function () {
return this[this.length - 1];
};
stack.lastTagNameEq = function (tagName) {
var last = this.last();
return last && last.tagName && last.tagName.toUpperCase() === tagName.toUpperCase();
};
stack.containsTagName = function (tagName) {
for (var i = 0, tok; tok = this[i]; i++) {
if (tok.tagName === tagName) {
return true;
}
}
return false;
};
return stack;
}
/**
* Return a readToken implementation that fixes input.
*
* @param {HtmlParser} parser The parser
* @param {Object} options Options for fixing
* @param {boolean} options.tagSoupFix True to fix tag soup scenarios
* @param {boolean} options.selfCloseFix True to fix self-closing tags
* @param {Function} readTokenImpl The underlying readToken implementation
* @returns {Function}
*/
function fixedReadTokenFactory(parser, options, readTokenImpl) {
var stack = newStack();
var handlers = {
startTag: function startTag(tok) {
var tagName = tok.tagName;
if (tagName.toUpperCase() === 'TR' && stack.lastTagNameEq('TABLE')) {
parser.prepend('<TBODY>');
prepareNextToken();
} else if (options.selfCloseFix && CLOSESELF.test(tagName) && stack.containsTagName(tagName)) {
if (stack.lastTagNameEq(tagName)) {
closeLast(parser, stack);
} else {
parser.prepend('</' + tok.tagName + '>');
prepareNextToken();
}
} else if (!tok.unary) {
stack.push(tok);
}
},
endTag: function endTag(tok) {
var last = stack.last();
if (last) {
if (options.tagSoupFix && !stack.lastTagNameEq(tok.tagName)) {
// cleanup tag soup
closeLast(parser, stack);
} else {
stack.pop();
}
} else if (options.tagSoupFix) {
// cleanup tag soup part 2: skip this token
readTokenImpl();
prepareNextToken();
}
}
};
function prepareNextToken() {
var tok = peekToken(parser, readTokenImpl);
if (tok && handlers[tok.type]) {
handlers[tok.type](tok);
}
}
return function fixedReadToken() {
prepareNextToken();
return correct(readTokenImpl());
};
}
/***/ }
/******/ ])
});
;

View File

@@ -0,0 +1,2 @@
<?php
Advanced_Ads_Pro_Module_Cache_Busting::get_instance();

View File

@@ -0,0 +1,355 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Framework\Utilities\Params;
/**
* Reduce the number of AJAX calls: during the first AJAX call, save the data (in cookies),
* that cannot be checked using only JS. Later, the passive cache-busting can check that data.
*
* There are 2 ways to update the data array:
* 1. Define the 'ADVANCED_ADS_PRO_USER_COOKIE_MAX_AGE' constant (in seconds).
* An ajax requests will be initiated from time to time to update the expired conditions of the ads on the page.
* 2. Use the "Update visitor conditions cache in the user's browsers" option (Settings > Pro > Cache Busting ) and update the page cache.
* An ajax request will be initiated to update all the conditions of the ads on the page.
*/
class Advanced_Ads_Pro_Cache_Busting_Server_Info {
/**
* Holds the Cache Busting class.
*
* @var Advanced_Ads_Pro_Module_Cache_Busting
*/
public $cache_busting;
/**
* The options array.
*
* @var array
*/
public $options;
/**
* How long the server info cookie will be stored maximum.
*
* @var int
*/
public $server_info_duration;
/**
* How long the server info cookie will be stored maximum.
*
* @var int
*/
public $vc_cache_reset;
/**
* If we are in a current AJAX call.
*
* @var bool
*/
public $is_ajax;
/**
* The constructor.
*
* @param Advanced_Ads_Pro_Module_Cache_Busting $cache_busting Cache Busting instance.
* @param array $options Option array.
*/
public function __construct( $cache_busting, $options ) {
$this->cache_busting = $cache_busting;
$this->options = $options;
$this->server_info_duration = defined( 'ADVANCED_ADS_PRO_USER_COOKIE_MAX_AGE' ) ? absint( ADVANCED_ADS_PRO_USER_COOKIE_MAX_AGE ) : MONTH_IN_SECONDS;
$this->vc_cache_reset = ! empty( $this->options['vc_cache_reset'] ) ? absint( $this->options['vc_cache_reset'] ) : 0;
$this->is_ajax = ! empty( $cache_busting->is_ajax );
new Advanced_Ads_Pro_Cache_Busting_Visitor_Info_Cookie( $this );
}
/**
* Get ajax request that will be used in case required cookies do not exist.
*
* @param array $ads An array of Ad objects.
* @param array $args Ad arguments.
*
* @return void|array
*/
public function get_ajax_for_passive_placement( $ads, $args, $elementid ) {
if ( ! $this->server_info_duration ) {
return;
}
if ( ! is_array( $ads ) ) {
$ads = [ $ads ];
}
$server_c = [];
foreach ( $ads as $ad ) {
$ad_server_c = $this->get_server_conditions( $ad );
if ( $ad_server_c ) {
$server_c = array_merge( $server_c, $ad_server_c );
}
}
if ( ! $server_c ) { return; }
$query = Advanced_Ads_Pro_Module_Cache_Busting::build_js_query( $args);
return [
'ajax_query' => Advanced_Ads_Pro_Module_Cache_Busting::get_instance()->get_ajax_query( array_merge( $query, [
'elementid' => $elementid,
'server_conditions' => $server_c
] ) ),
'server_info_duration' => $this->server_info_duration,
'server_conditions' => $server_c,
];
}
/**
* Get server conditions of the ad.
*
* @param $ad Ad
* @return array
*/
private function get_server_conditions( Ad $ad ) {
$visitors = $ad->get_visitor_conditions();
$visitors = ! empty( $visitors ) && is_array( $visitors ) ? array_values( $visitors ) : [];
$result = [];
foreach ( $visitors as $k => $visitor ) {
if ( $info = $this->get_server_condition_info( $visitor ) ) {
$visitor_to_add = array_intersect_key( $visitor, [ 'type' => true, $info['hash_fields'] => true ] );
$result[ $info['hash'] ] = $visitor_to_add;
}
}
return $result;
}
/**
* Get info about the server condition.
*
* @param array $visitor Visitor condition.
* @return array|void info about server condition.
*/
public function get_server_condition_info( $visitor ) {
if ( ! isset( $visitor['type'] ) ) {
return;
}
$conditions = $this->get_all_server_conditions();
if ( ! isset( $conditions[ $visitor['type'] ]['passive_info']['function'] ) ) {
// It's not a server condition.
return;
}
$info = $conditions[ $visitor['type'] ]['passive_info'];
$hash = $visitor['type'];
// Add unique fields set on the Ad edit page.
// This allows us to to have several conditions of the same type.
if ( isset( $info['hash_fields'] ) && isset( $visitor[ $info['hash_fields'] ] ) ) {
$hash .= '_' . $visitor[ $info['hash_fields'] ];
}
// Allow the administrator to remove all cookies in the user's browsers.
$hash .= '_' . $this->vc_cache_reset;
$hash = substr( md5( $hash ), 0, 10 );
return [ 'hash' => $hash, 'function' => $info['function'], 'hash_fields' => $info['hash_fields'] ];
}
/**
* Get all server conditions.
*/
public function get_all_server_conditions() {
if ( ! $this->server_info_duration ) {
return [];
}
if ( ! did_action( 'init' ) ) {
// All conditions should be ready.
trigger_error( sprintf( '%1$s was called incorrectly', 'Advanced_Ads_Pro_Cache_Busting_Server_Info::get_all_server_conditions' ) );
}
$r = [];
foreach ( Advanced_Ads_Visitor_Conditions::get_instance()->conditions as $name => $condition ) {
if ( isset( $condition['passive_info'] ) ) {
$r[ $name ] = $condition;
}
}
return $r;
}
}
/**
* Cache Bust: Visitor info cookie
*/
class Advanced_Ads_Pro_Cache_Busting_Visitor_Info_Cookie {
// Note: hard-coded in JS.
const VISITOR_INFO_COOKIE_NAME = 'advanced_ads_visitor';
/**
* Holds the server info class.
*
* @var Advanced_Ads_Pro_Cache_Busting_Server_Info
*/
private $server_info;
public function __construct( $server_info ) {
$this->server_info = $server_info;
if ( ! $this->can_set_cookie() ) {
// Remove cookie.
if ( $this->parse_existing_cookies() ) {
$this->set_cookie( false );
}
return;
}
if ( $this->server_info->is_ajax ) {
add_action( 'init', [ $this, 'add_server_info' ], 50 );
}
if ( ! empty( $this->server_info->options['vc_cache_reset_actions']['login'] ) ) {
add_action( 'wp_logout', [ $this, 'log_in_out' ] );
add_action( 'set_auth_cookie', [ $this, 'log_in_out' ] );
}
}
/**
* Create cookies during AJAX requests.
*/
public function add_server_info() {
$request = Params::request( 'deferedAds', [], FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
if ( empty( $request ) ) {
return;
}
$e_cookie = $n_cookie = $this->parse_existing_cookies();
// Parse ajax request.
foreach ( $request as $query ) {
if ( ! isset( $query['ad_method'] ) || $query['ad_method'] !== 'placement' || empty( $query['server_conditions'] ) ) {
// The query does not have server conditions.
continue;
}
// Prepare new cookies to save.
$n_cookie = $this->prepare_new_cookies( $query['server_conditions'], $n_cookie );
}
$n_cookie['vc_cache_reset'] = $this->server_info->vc_cache_reset;
if ( $n_cookie !== $e_cookie ) {
$this->set_cookie( $n_cookie );
}
}
/**
* Get correct and not obsolete conditions.
*/
private function parse_existing_cookies() {
$n_cookie = [];
if ( Params::cookie( self::VISITOR_INFO_COOKIE_NAME ) ) {
$n_cookie['vc_cache_reset'] = $this->server_info->vc_cache_reset;
$e_cookie = Params::cookie( self::VISITOR_INFO_COOKIE_NAME );
$e_cookie = wp_unslash( $e_cookie );
$e_cookie = json_decode( $e_cookie, true );
if ( isset( $e_cookie['browser_width'] ) ) {
$n_cookie['browser_width'] = $e_cookie['browser_width'];
}
if ( isset( $e_cookie['vc_cache_reset'] ) && absint( $e_cookie['vc_cache_reset'] ) < $this->server_info->vc_cache_reset ) {
// The cookie has been reset on the Settings page.
return $n_cookie;
}
if ( empty( $e_cookie['conditions'] ) || ! is_array( $e_cookie['conditions'] ) ) {
return $n_cookie;
}
foreach ( $e_cookie['conditions'] as $cond_name => $hashes ) {
foreach ( (array) $hashes as $hash => $item ) {
// Do not add outdated conditions.
if ( isset( $item['time'] ) && ( absint( $item['time'] ) + $this->server_info->server_info_duration ) > time() ) {
$n_cookie['conditions'][ $cond_name ][ $hash ] = $item;
}
}
}
}
return $n_cookie;
}
/**
* Prepare new conditions to save.
*
* @param array $visitors New visitor conditions to add to cookie.
* @param array $n_cookie Existing visitor conditions from cookie.
*
* @return array $n_cookie New cookie.
*/
public function prepare_new_cookies( $visitors, $n_cookie = [] ) {
foreach ( (array) $visitors as $visitor ) {
$info = $this->server_info->get_server_condition_info( $visitor );
if ( ! $info ) { continue; }
if ( isset( $n_cookie['conditions'][ $visitor['type'] ][ $info['hash'] ] ) ) { continue; }
$n_cookie['conditions'][ $visitor['type'] ][ $info['hash'] ] = [
'data' => call_user_func( $info['function'], $visitor ),
'time' => time(),
];
}
return $n_cookie;
}
/**
* Check if the cookie can be set.
*/
public function can_set_cookie() {
return $this->server_info->server_info_duration;
}
/**
* Set cookie.
*
* @param array|bool $cookie Cookie.
*/
public function set_cookie( $cookie ) {
if ( ! $cookie ) {
setrawcookie( self::VISITOR_INFO_COOKIE_NAME, '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN );
return;
}
$cookie = json_encode( $cookie );
$cookie = rawurlencode( $cookie );
if ( strlen( $cookie ) > 4096 ) {
Advanced_Ads::log( 'The cookie size is too large' );
return;
}
// Prevent spaces from being converted to '+'
setrawcookie( self::VISITOR_INFO_COOKIE_NAME, $cookie, time() + $this->server_info->server_info_duration, COOKIEPATH, COOKIE_DOMAIN );
}
/**
* Remove server info on log in/out.
*/
public function log_in_out() {
$server_conditions = $this->server_info->get_all_server_conditions();
$n_cookie = $this->parse_existing_cookies();
if ( isset( $n_cookie['conditions'] ) ) {
foreach ( (array) $n_cookie['conditions'] as $cond_name => $cond ) {
if ( isset( $server_conditions[ $cond_name ]['passive_info']['remove'] )
&& $server_conditions[ $cond_name ]['passive_info']['remove'] === 'login' ) {
unset ( $n_cookie['conditions'][ $cond_name ] );
}
}
}
$this->set_cookie( $n_cookie );
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* Render CB settings.
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
$options = Advanced_Ads_Pro::get_instance()->get_options();
$module_enabled = ! empty( $options['cache-busting']['enabled'] );
$method = ( isset( $options['cache-busting']['default_auto_method'] ) && 'ajax' === $options['cache-busting']['default_auto_method'] ) ? 'ajax' : 'passive';
$fallback_method = ( isset( $options['cache-busting']['default_fallback_method'] ) && 'disable' === $options['cache-busting']['default_fallback_method'] ) ? 'disable' : 'ajax';
$passive_all = ! empty( $options['cache-busting']['passive_all'] );
$vc_cache_reset = ! empty( $options['cache-busting']['vc_cache_reset'] ) ? (int) $options['cache-busting']['vc_cache_reset'] : 0;
$vc_cache_reset_on_login = ! empty( $options['cache-busting']['vc_cache_reset_actions']['login'] );
?>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][enabled]" id="advanced-ads-pro-cache-busting-enabled" type="checkbox" value="1" <?php checked( $module_enabled ); ?> class="advads-has-sub-settings" />
<label for="advanced-ads-pro-cache-busting-enabled" class="description">
<?php esc_html_e( 'Activate module.', 'advanced-ads-pro' ); ?>
</label>
<a href="https://wpadvancedads.com/manual/cache-busting/?utm_source=advanced-ads&utm_medium=link&utm_campaign=pro-cb-manual'; ?>" target="_blank" class="advads-manual-link"><?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?></a>
<div class="advads-sub-settings">
<h4><?php esc_html_e( 'Default option', 'advanced-ads-pro' ); ?></h4>
<p class="description"><?php esc_html_e( 'Choose which method to use when a placement needs cache busting and the option is set to “auto”.', 'advanced-ads-pro' ); ?></p>
<label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][default_auto_method]" type="radio" value="passive"
<?php
checked( $method, 'passive' );
?>
/><?php esc_html_e( 'passive', 'advanced-ads-pro' ); ?>
</label>
<label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][default_auto_method]" type="radio" value="ajax"
<?php
checked( $method, 'ajax' );
?>
/><?php esc_html_e( 'AJAX', 'advanced-ads-pro' ); ?>
</label>
<p><label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][passive_all]" type="checkbox" value="1"
<?php
checked( $passive_all, 1 );
?>
/><?php esc_html_e( 'Force passive cache busting', 'advanced-ads-pro' ); ?>
</label></p>
<p class="description">
<?php
esc_html_e( 'By default, cache busting only works through placements.', 'advanced-ads-pro' );
echo '&nbsp;';
esc_html_e( 'Enable passive cache busting for all ads and groups which are not delivered through a placement, if possible.', 'advanced-ads-pro' );
?>
</p>
<?php if ( method_exists( wp_get_theme(), 'is_block_theme' ) && wp_get_theme()->is_block_theme() ) : ?>
<div class="notice advads-notice inline" style="margin:5px 1px 7px">
<p>
<?php
printf(
// translators: 1 opening link tag. 2 closing tag.
esc_html__( 'This option does not work with the current active theme and %1$sblock themes%2$s in general.', 'advanced-ads-pro' ),
'<a href="https://wordpress.org/documentation/article/block-themes/" target="_blank">',
'</a>'
);
?>
</p>
</div>
<?php endif; ?>
<h4><?php esc_html_e( 'Fallback option', 'advanced-ads-pro' ); ?></h4>
<p class="description"><?php esc_html_e( 'Choose the fallback if “passive“ cache busting is not possible.', 'advanced-ads-pro' ); ?></p>
<label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][default_fallback_method]" type="radio" value="ajax"
<?php
checked( $fallback_method, 'ajax' );
?>
/><?php esc_html_e( 'AJAX', 'advanced-ads-pro' ); ?>
</label>
<label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][default_fallback_method]" type="radio" value="disable"
<?php
checked( $fallback_method, 'disable' );
?>
/><?php esc_html_e( 'off', 'advanced-ads-pro' ); ?>
</label>
<input id="advads-pro-vc-hash" name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][vc_cache_reset]" type="hidden" value="
<?php
echo esc_attr( $vc_cache_reset );
?>
" />
<h4><?php esc_html_e( 'Visitor profile', 'advanced-ads-pro' ); ?></h4>
<p class="description">
<?php
esc_html_e( 'Advanced Ads stores some user information in the users browser to limit the number of AJAX requests for cache busting. Manual', 'advanced-ads-pro' );
?>
<a href="https://wpadvancedads.com/manual/cache-busting/?utm_source=advanced-ads&utm_medium=link&utm_campaign=visitor-profile#Visitor_profile'; ?>" target="_blank" class="dashicons dashicons-external"></a>
</p>
<br/><button type="button" id="advads-pro-vc-hash-change" class="button-secondary"><?php esc_html_e( 'Update visitor profile', 'advanced-ads-pro' ); ?></button>
<p id="advads-pro-vc-hash-change-ok" class="advads-success-message" style="display:none;">
<?php esc_html_e( 'Updated', 'advanced-ads-pro' ); ?>, <?php esc_html_e( 'You might need to update the page cache if you are using one.', 'advanced-ads-pro' ); ?></span>
</p>
<p id="advads-pro-vc-hash-change-error" class="advads-notice-inline advads-error" style="display:none;"><?php esc_html_e( 'An error occurred', 'advanced-ads-pro' ); ?></p>
<input type="hidden" id="advads-pro-reset-vc-cache-nonce" value="<?php echo esc_attr( wp_create_nonce( 'advads-pro-reset-vc-cache-nonce' ) ); ?>" />
<p><label>
<input name="<?php echo esc_attr( Advanced_Ads_Pro::OPTION_KEY ); ?>[cache-busting][vc_cache_reset_actions][login]" type="checkbox" value="1" <?php checked( $vc_cache_reset_on_login, 1 ); ?> />
<?php esc_html_e( 'Update visitor profile when user logs in or out', 'advanced-ads-pro' ); ?>
</label></p>
</div>

View File

@@ -0,0 +1,34 @@
<?php //phpcs:ignoreFile
/**
* Setting check ad template
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
* @since 2.26.0
*/
?>
<div id="advads-cache-busting-check-wrap">
<span id="advads-cache-busting-error-result" class="advads-notice-inline advads-error" style="display:none;">
<?php
printf(
/* translators: %s link to manual */
__( 'The code of this ad might not work properly with activated cache-busting. <a href="%s" target="_blank">Manual</a>', 'advanced-ads-pro' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'https://wpadvancedads.com/manual/cache-busting/#advads-passive-compatibility-warning'
);
?>
</span>
<input type="hidden" id="advads-cache-busting-possibility" name="advanced_ad[cache-busting][possible]" value="true" />
</div>
<?php if ( ! $ad->is_type( 'adsense' ) ) : ?>
<script>
jQuery( document ).ready(function() {
if ( typeof advads_cb_check_ad_markup !== 'undefined' ){
var ad_content = <?php echo json_encode( $ad->prepare_output( $ad ) ); ?>;
advads_cb_check_ad_markup( ad_content );
}
});
</script>
<?php
endif;

View File

@@ -0,0 +1,44 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin class for the Advanced Ads Pro Click Fraud Protection module.
*
* @package AdvancedAds\Pro\Modules\Visitor_Conditions
* @author Advanced Ads <info@wpadvancedads.com>
*/
/**
* Class Advanced_Ads_Pro_Module_CFP_Admin
*/
class Advanced_Ads_Pro_Module_CFP_Admin {
/**
* The constructor
*/
public function __construct() {
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ] );
}
/**
* Add settings for the module.
*
* @return void
*/
public function settings_init(): void {
add_settings_field(
'module-cfp',
__( 'Click Fraud Protection', 'advanced-ads-pro' ),
[ $this, 'render_settings' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings',
Advanced_Ads_Pro::OPTION_KEY . '_modules-enable'
);
}
/**
* Render the settings.
*
* @return void
*/
public function render_settings(): void {
include_once __DIR__ . '/views/settings.php';
}
}

View File

@@ -0,0 +1,2 @@
<?php
new Advanced_Ads_Pro_Module_CFP_Admin;

View File

@@ -0,0 +1,376 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Abstracts\Group;
use AdvancedAds\Framework\Utilities\Params;
/**
* Click Fraud Protection class.
*/
class Advanced_Ads_Pro_Module_CFP {
public $module_enabled = false;
/**
* Constructor.
*/
public function __construct() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
$this->module_enabled = isset( $options['cfp']['enabled'] ) && $options['cfp']['enabled'];
add_filter( 'advanced-ads-output-wrapper-options', [ $this, 'add_wrapper' ], 25, 2 );
add_filter( 'advanced-ads-ad-output', [ $this, 'ad_output' ], 99, 2 );
add_action( 'wp_enqueue_scripts', [ $this, 'add_frontend_script_data' ], 11 );
add_filter( 'advanced-ads-can-display-ad', [ $this, 'can_display' ], 10, 3 );
// add visitor conditions
add_filter( 'advanced-ads-visitor-conditions', [ $this, 'add_visitor_conditions' ] );
add_filter( 'advanced-ads-js-visitor-conditions', [ $this, 'add_to_passive_cache_busting' ] );
//Add new node to 'Ad health' if the user is banned.
add_filter( 'advanced-ads-ad-health-nodes', [ $this, 'add_ad_health_node' ] );
// delete ban cookie before any output
add_action( 'init', [ $this, 'delete_ban_cookie' ], 50 );
if ( $this->module_enabled ) {
add_filter( 'advanced-ads-output-wrapper-options', [ $this, 'add_wrapper_options' ], 10, 2 );
add_filter( 'advanced-ads-output-wrapper-options-group', [ $this, 'add_wrapper_options_group' ], 10, 2 );
add_filter( 'advanced-ads-pro-ad-needs-backend-request', [ $this, 'ad_needs_backend_request' ], 10, 1 );
}
}
/**
* determine cookies parameter (domain or path)
*/
private function get_path_and_domain() {
$path = '';
$site_url = site_url();
$domain = '';
// get host name form server first before getting client side value
$server_name = Params::server( 'SERVER_NAME' );
$host_name = ! empty( $server_name ) ? $server_name : Params::server( 'HTTP_HOST' );
if ( empty( $host_name ) ) {
return [
'path' => '/',
'domain' => '',
];
}
$expl_url = explode( '.', $site_url );
if ( 2 < count( $expl_url ) ) {
// using subdomain
$domain = $host_name;
}
$path = explode( $host_name, $site_url );
if ( isset( $path[1] ) ) {
$path = $path[1];
} else {
$path = $path[0];
}
return [
'path' => $path,
'domain' => $domain,
];
}
private function get_conditions_options( Ad $ad ) {
if ( empty( $ad->get_visitor_conditions() ) ) {
return [];
}
$results = [];
foreach( $ad->get_visitor_conditions() as $cond ) {
if ( isset( $cond['type'] ) && 'ad_clicks' == $cond['type'] ) {
$results[] = $cond;
}
}
return $results;
}
/**
* Notify passive cache-busting that this condition can be checked using JavaScript.
*/
public function add_to_passive_cache_busting( $conditions ) {
$conditions[] = 'ad_clicks';
return $conditions;
}
/**
* add the visitor condition
*/
public function add_visitor_conditions( $conditions ) {
$conditions['ad_clicks'] = [
'label' => __( 'max. ad clicks', 'advanced-ads-pro' ),
'description' => __( 'Display ad only if a click limit has not been reached.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'visitor_conditions_metabox' ], // callback to generate the metabox
'check' => [ $this, 'visitor_conditions_check' ] // callback for frontend check
];
return $conditions;
}
/**
* render the markup for visitor condition
*
* @param array $options Options of the condition.
* @param int $index Index of the condition.
* @param string $form_name Name of the form, falls back to class constant.
*/
public function visitor_conditions_metabox( $options, $index = 0, $form_name = '' ) {
if ( ! isset ( $options['type'] ) || '' === $options['type'] ) { return; }
$type_options = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
if ( ! isset( $type_options[ $options['type'] ] ) ) {
return;
}
// form name basis
$name = Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions::get_form_name_with_index( $form_name, $index );
// options
$limit = isset( $options['limit'] ) ? Advanced_Ads_Pro_Utils::absint( $options['limit'], 1 ) : 1;
$expiration = isset( $options['expiration'] ) ? Advanced_Ads_Pro_Utils::absint( $options['expiration'], 1 ) : 1;
?>
<input type="hidden" name="<?php echo esc_attr( $name ); ?>[value]" value="1"/>
<input type="number" name="<?php echo esc_attr( $name ); ?>[limit]" value="<?php echo esc_attr( $limit ); ?>" required="required" min="1" step="1" />
<?php _e( 'within', 'advanced-ads-pro' ); ?>
<input type="number" name="<?php echo $name; ?>[expiration]" value="<?php echo $expiration; ?>" required="required" min="1" step="1" />
<?php _e( 'hours', 'advanced-ads-pro' ); ?>
<input type="hidden" name="<?php echo $name; ?>[type]" value="<?php echo $options['type']; ?>"/>
<p class="description">
<?php echo esc_html( $type_options[ $options['type'] ]['description'] ); ?>
<a href="https://wpadvancedads.com/manual/max-ad-clicks/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-max-ad-clicks" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
<label class="advads-conditions-table-width-100">
<input type="checkbox" name="<?php echo esc_attr( $name ); ?>[exclude-from-global]" value="1" <?php checked( ! empty( $options['exclude-from-global'] ) ); ?> />
<?php esc_html_e( 'Ignore global Click Fraud Protection', 'advanced-ads-pro' ); ?>
<a href="https://wpadvancedads.com/manual/click-fraud-protection/#utm_source=advanced-ads&utm_medium=link&utm_campaign=pro-cfp-manual'; ?>" target="_blank" class="advads-manual-link">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</label>
<?php
}
/**
* delete ban cookie
*/
public function delete_ban_cookie() {
if ( !$this->module_enabled && Params::cookie( 'advads_pro_cfp_ban' ) && !headers_sent() ) {
$pnd = $this->get_path_and_domain();
$path = '' != $pnd['path'] ? $pnd['path'] : '/';
if ( '' != $pnd['domain'] ) {
setcookie( 'advads_pro_cfp_ban', '0', time() - 3600, $path, $pnd['domain'] );
} else {
setcookie( 'advads_pro_cfp_ban', '0', time() - 3600, $path );
}
}
}
/**
* front end check for the visitor condition
*
* @param array $options Options of the condition.
* @param Ad $ad Ad object.
*/
public function visitor_conditions_check( $options = [], $ad = false ) {
$ad_click = Params::cookie( 'advanced_ads_ad_clicks_' . $ad->get_id() );
if ( $ad_click ) {
$cval = json_decode( stripslashes( $ad_click ), true );
$update_cookie = false;
$now = time();
foreach( $cval as $key => $value ) {
// check against the right value in case of multiple ad clicks conditions
if ( '_' . $options['expiration'] == $key ) {
if ( $now >= absint( $value['ttl'] ) ) {
// expired TTL - just skip, cookie will be updated client side
return true;
} else {
if ( absint( $value['count'] ) >= absint( $options['limit'] ) ) {
return false;
}
}
}
}
}
return true;
}
/**
* determine if ads should be hidden
*
* @param bool $can_display Whether the current ad can be displayed.
* @param Ad $ad The ad object.
* @param array $check_options Check options.
* @return bool
*/
public function can_display( $can_display, $ad, $check_options ) {
if ( ! $can_display || ! $this->module_enabled || ! empty( $check_options['passive_cache_busting'] ) ) {
return $can_display;
}
// Check if the global setting is ignored.
foreach ( $this->get_conditions_options( $ad ) as $condition ) {
if ( ! empty( $condition['exclude-from-global'] ) ) {
return true;
}
}
// Check if the user is banned
return empty( Params::cookie( 'advads_pro_cfp_ban' ) );
}
/**
* Enqueue front end script.
*/
public function add_frontend_script_data() {
// Do not enqueue on AMP pages.
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return;
}
$options = Advanced_Ads_Pro::get_instance()->get_options();
$pnd = $this->get_path_and_domain();
wp_localize_script( Advanced_Ads_Pro::FRONTEND_SCRIPT_HANDLE, 'advadsCfpInfo', [
'cfpExpHours' => isset( $options['cfp']['cookie_expiration'] ) ? $options['cfp']['cookie_expiration'] : 3,
'cfpClickLimit' => isset( $options['cfp']['click_limit'] ) ? absint( $options['cfp']['click_limit'] ) : 3,
'cfpBan' => isset( $options['cfp']['ban_duration'] ) ? $options['cfp']['ban_duration'] : 7,
'cfpPath' => $pnd['path'],
'cfpDomain' => $pnd['domain'],
'cfpEnabled' => isset( $options['cfp']['enabled'] ),
] );
}
/**
* Add the JS that adds this ad to the list of CFP.
*
* @param string $output Ad output.
* @param Ad $ad Ad object.
* @return string
*/
public function ad_output( $output, $ad ) {
// Do not enqueue on AMP pages.
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return $output;
}
$cond = $this->get_conditions_options( $ad );
if ( !empty( $cond ) || $this->module_enabled ) {
$output .= '<script type="text/javascript">;new advadsCfpAd( ' . $ad->get_id() . ' );</script>';
}
return $output;
}
/**
* add the HTML attribute for front end JS
*
* @param array $wrapper Wrapper options.
* @param Ad $ad The ad object.
*/
public function add_wrapper( $wrapper, $ad ) {
$cond = $this->get_conditions_options( $ad );
if ( !empty( $cond ) || $this->module_enabled ) {
// Iframe or `a` tag of the ad
$wrapper['data-cfpa'] = $ad->get_id();
// Ad wrapper.
$wrapper['data-cfpw'] = $ad->get_id();
// print all expiration hours
if ( !empty( $cond ) ) {
$hours = [];
foreach( $cond as $_c ) {
$hours[ '_' . (float) $_c['expiration'] ] = (int) $_c['limit'];
if ( isset( $_c['exclude-from-global'] ) ) {
$wrapper['data-cfp-exclude'] = true;
}
}
// Click limits.
$wrapper['data-cfph'] = wp_json_encode( $hours );
}
}
return $wrapper;
}
/**
* Add new node to 'Ad health' if the user is banned.
*
* @param array $nodes.
* @return bool array $nodes.
*/
public function add_ad_health_node( $nodes ) {
if ( Params::cookie( 'advads_pro_cfp_ban' ) ) {
$nodes[] = [ 'type' => 1, 'data' => [
'parent' => 'advanced_ads_ad_health',
'id' => 'advanced_ads_ad_health_cfp_enabled',
'title' => __( 'Click Fraud Protection enabled', 'advanced-ads-pro' ),
'href' => 'https://wpadvancedads.com/manual/click-fraud-protection',
'meta' => [
'class' => 'advanced_ads_ad_health_warning',
'target' => '_blank'
]
] ];
}
return $nodes;
}
/**
* Add the data attribute to the top level wrapper when it is an ad wrapper.
*
* @param array $options Wrapper options.
* @param Ad $ad Ad object.
*
* @return array
*/
public function add_wrapper_options( $options, Ad $ad ) {
if ( ! empty( $ad->get_prop( 'is_top_level' ) ) ) {
$options['data-cfptl'] = true;
}
return $options;
}
/**
* Add the data attribute to the top level wrapper when it is a group wrapper.
*
* @param array $options Wrapper options.
* @param Group $group group object.
*
* @return array
*/
public function add_wrapper_options_group( $options, Group $group ) {
if ( ! empty( $group->get_prop( 'ad_args.is_top_level' ) ) ) {
$options['data-cfptl'] = true;
}
return $options;
}
/**
* Enable cache-busting if the module is enabled.
*
* @param string $check What cache-busting type is needed.
*
* @return string $check What cache-busting type is needed.
*/
public function ad_needs_backend_request( $check ) {
if ( 'static' === $check ) {
return 'passive';
}
return $check;
}
}

View File

@@ -0,0 +1,2 @@
<?php
new Advanced_Ads_Pro_Module_CFP;

View File

@@ -0,0 +1,24 @@
<?php
$options = Advanced_Ads_Pro::get_instance()->get_options();
$module_enabled = isset( $options['cfp']['enabled'] ) && $options['cfp']['enabled'];
$click_limit = isset( $options['cfp']['click_limit'] )? Advanced_Ads_Pro_Utils::absint( $options['cfp']['click_limit'], 1 ) : 3;
$cookie_exp = isset( $options['cfp']['cookie_expiration'] )? Advanced_Ads_Pro_Utils::absint( $options['cfp']['cookie_expiration'], 1 ) : 3;
$ban_duration = isset( $options['cfp']['ban_duration'] )? Advanced_Ads_Pro_Utils::absint( $options['cfp']['ban_duration'], 1 ) : 7;
?>
<input name="<?php echo Advanced_Ads_Pro::OPTION_KEY; ?>[cfp][enabled]" type="checkbox" value="1" <?php checked( $module_enabled ); ?> id="advanced-ads-pro-cfp-enabled" class="advads-has-sub-settings" />
<label for="advanced-ads-pro-cfp-enabled" class="description">
<?php esc_html_e( 'Activate module.', 'advanced-ads-pro' ); ?>
</label>
<a href="https://wpadvancedads.com/manual/click-fraud-protection/?utm_source=advanced-ads&utm_medium=link&utm_campaign=pro-cfp-manual'; ?>" target="_blank" class="advads-manual-link"><?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?></a>
<div class="advads-sub-settings">
<br />
<input type="number" min="1" name="<?php echo Advanced_Ads_Pro::OPTION_KEY; ?>[cfp][click_limit]" style="width:4em;" value="<?php echo $click_limit; ?>" />
<span><?php _e( 'Allowed clicks on a single ad before it is removed', 'advanced-ads-pro' ); ?></span>
<br/>
<input type="number" min="1" name="<?php echo Advanced_Ads_Pro::OPTION_KEY; ?>[cfp][cookie_expiration]" style="width:4em;" value="<?php echo $cookie_exp; ?>" />
<span><?php _e( 'Period in which the click limit should be reached ( in hours )', 'advanced-ads-pro' ); ?></span>
<br/>
<input type="number" min="1" name="<?php echo Advanced_Ads_Pro::OPTION_KEY; ?>[cfp][ban_duration]" style="width:4em;" value="<?php echo $ban_duration; ?>" />
<span><?php _e( 'Period for which to hide the ad ( in days )', 'advanced-ads-pro' ); ?></span>
</div>

View File

@@ -0,0 +1,197 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Duplicate ads in admin class
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
use AdvancedAds\Constants;
use AdvancedAds\Framework\Utilities\Params;
use AdvancedAds\Utilities\Conditional;
/**
* Class Advanced_Ads_Pro_Module_Duplicate_Ads_Admin
* Admin logic to duplicate an existing ads.
*/
class Advanced_Ads_Pro_Module_Duplicate_Ads_Admin {
/**
* Advanced_Ads_Pro_Module_Duplicate_Ads_Admin constructor.
*/
public function __construct() {
add_action( 'admin_init', [ $this, 'admin_init' ] );
add_action( 'admin_action_advanced_ads_duplicate_ad', [ $this, 'duplicate_ad' ] );
}
/**
* On admin init
*/
public function admin_init() {
// add Duplicate link to ad overview list.
add_filter( 'post_row_actions', [ $this, 'render_duplicate_link' ], 10, 2 );
// add Duplicate link to post submit box.
add_action( 'post_submitbox_start', [ $this, 'render_duplicate_link_in_submit_box' ] );
}
/**
* Add the link to action list for post_row_actions
*
* @param array $actions list of existing actions.
* @param object $post Post object.
*
* @return array with actions.
*/
public function render_duplicate_link( $actions, $post ) {
if (
isset( $post->post_type ) &&
Constants::POST_TYPE_AD === $post->post_type &&
Conditional::user_can( 'advanced_ads_edit_ads' )
) {
$actions['copy-ad'] = self::get_duplicate_link( $post->ID );
}
return $actions;
}
/**
* Add the link to the submit box on the ad edit screen.
*/
public function render_duplicate_link_in_submit_box() {
global $post;
if (
isset( $post->post_type ) &&
'edit' === $post->filter &&
Constants::POST_TYPE_AD === $post->post_type &&
Conditional::user_can( 'advanced_ads_edit_ads' ) ) {
?>
<div>
<?php echo self::get_duplicate_link( $post->ID ); // phpcs:ignore ?>
</div>
<?php
}
}
/**
* Build the duplicate URL
*
* @param int $ad_id ID of the ad.
*
* @return string
*/
public static function get_duplicate_link( $ad_id ) {
$action = '?action=advanced_ads_duplicate_ad&amp;ad_id=' . $ad_id;
$url = wp_nonce_url( admin_url( 'admin.php' . $action ), 'duplicate-ad-' . $ad_id );
return '<a href="' . $url . '" title="' . esc_attr__( 'Create a copy of this ad', 'advanced-ads-pro' ) . '">' . esc_html__( 'Duplicate', 'advanced-ads-pro' ) . '</a>';
}
/**
* Save a copy of an ad using the same status as the original ad.
*/
public function duplicate_ad() {
$action = Params::get( 'action' );
$ad_id = Params::get( 'ad_id', 0, FILTER_VALIDATE_INT );
if (
! $action
|| 'advanced_ads_duplicate_ad' !== $action
|| ! $ad_id
|| ! Conditional::user_can( 'advanced_ads_edit_ads' )
) {
return;
}
check_admin_referer( 'duplicate-ad-' . $ad_id );
$ad = get_post( $ad_id );
// copy the ad.
if ( isset( $ad ) && null !== $ad ) {
// add copy logic.
$new_id = $this->create_copy( $ad );
// redirect to the ad edit page of the new ad.
wp_safe_redirect( admin_url( 'post.php?action=edit&post=' . $new_id ) );
}
}
/**
* Create the copy of an ad.
*
* @param WP_POST $ad ad object.
*
* @return mixed
*/
public function create_copy( $ad ) {
// return original ad ID if we are not using the correct post type.
if ( empty( $ad->post_type ) || Constants::POST_TYPE_AD !== $ad->post_type ) {
return $ad->ID;
}
$new_ad = [];
$new_ad['post_type'] = $ad->post_type;
$new_ad['post_status'] = isset( $ad->post_status ) ? $ad->post_status : 'draft';
$copy_suffix = ' (' . _x( 'copy', 'noun', 'advanced-ads-pro' ) . ' at ' . current_time( 'Y-m-d H:i:s' ) . ')';
$new_ad['post_title'] = isset( $ad->post_title ) ? $ad->post_title . $copy_suffix : $copy_suffix;
// use current user as author not really needed though.
$new_ad_author = wp_get_current_user();
$new_ad['post_author'] = $new_ad_author->ID;
// copy content.
$new_ad['post_content'] = $ad->post_content;
// save the ad. WordPress will handle missing fields.
$new_ad_id = wp_insert_post( wp_slash( $new_ad ) );
/**
* Handle post meta of the ad
*/
/**
* Copy the meta information of a post to another post
*/
$post_meta_keys = get_post_custom_keys( $ad->ID );
if ( empty( $post_meta_keys ) ) {
return;
}
// handle exceptions for post meta key that should not be copied.
$meta_blacklist[] = '_edit_lock'; // edit lock.
$meta_blacklist[] = '_edit_last'; // edit last.
$meta_blacklist[] = 'slide_template'; // unknown plugin.
$meta_blacklist[] = 'tps_options'; // unknown plugin.
$meta_blacklist[] = 'isc_post_images'; // Image Source Control plugin.
$meta_blacklist[] = '_wp_old_slug'; // WooCommerce.
$meta_blacklist[] = '_vc_post_settings'; // Visual Composer.
$meta_blacklist[] = 'post_views_count'; // unknown plugin.
$meta_blacklist[] = 'advanced_ads_selling_order'; // Advanced Ads Selling Ads add-on (order data).
$meta_blacklist[] = 'advanced_ads_selling_order_item'; // Advanced Ads Selling Ads add-on (order item data).
// allow other plugins to filter the list.
$meta_blacklist = apply_filters( 'advanced_ads_pro_duplicate_meta_blacklist', $meta_blacklist );
$meta_keys = array_diff( $post_meta_keys, $meta_blacklist );
// get values and add them to the ad.
foreach ( $meta_keys as $meta_key ) {
$meta_values = get_post_custom_values( $meta_key, $ad->ID );
foreach ( $meta_values as $meta_value ) {
$meta_value = maybe_unserialize( $meta_value );
// Tracking add-on: remove value for public tracking ID because it needs to be unique per ad.
if ( 'advanced_ads_ad_options' === $meta_key && isset( $meta_value['tracking']['public-id'] ) ) {
unset( $meta_value['tracking']['public-id'] );
}
add_post_meta( $new_ad_id, $meta_key, $meta_value );
}
}
return $new_ad_id;
}
}

View File

@@ -0,0 +1,4 @@
<?php
new Advanced_Ads_Pro_Module_Duplicate_Ads_Admin();

View File

@@ -0,0 +1,5 @@
<?php
class Advanced_Ads_Pro_Module_Duplicate_Ads {
}

View File

@@ -0,0 +1,3 @@
<?php
new Advanced_Ads_Pro_Module_Duplicate_Ads;

View File

@@ -0,0 +1,150 @@
<?php // phpcs:disable WordPress.Files.FileName.InvalidClassFileName
/**
* Admin functionality for the adblocker module.
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
use AdvancedAds\Utilities\Conditional;
/**
* Class definition.
*/
class Advanced_Ads_Pro_Module_Extended_Adblocker_Admin {
/**
* Setting options
*
* @var array
*/
private $options = [];
/**
* Constructor
*
* @return void
*/
public function __construct() {
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ], 9 );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
}
/**
* Initializes the settings for the adblocker module.
*
* @return void
*/
public function settings_init(): void {
$this->options = Advanced_Ads::get_instance()->get_adblocker_options();
add_settings_section(
'advanced_ads_adblocker_extended_setting_section',
'',
null,
ADVADS_SETTINGS_ADBLOCKER
);
add_settings_field(
'extended_ab',
__( 'Ad blocker countermeasures', 'advanced-ads-pro' ),
[ $this, 'render_settings_activate_extended' ],
ADVADS_SETTINGS_ADBLOCKER,
'advanced_ads_adblocker_extended_setting_section'
);
}
/**
* Enqueue admin scripts.
*
* @return void
*/
public function enqueue_admin_scripts(): void {
if ( ! Conditional::is_screen_advanced_ads() ) {
return;
}
$screen = get_current_screen();
if ( ! isset( $screen->id ) || 'advanced-ads_page_advanced-ads-settings' !== $screen->id ) {
return;
}
$admin_js = plugin_dir_url( __FILE__ ) . ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG
? 'assets/js/admin.js'
: 'assets/js/admin.min.js' );
wp_enqueue_script( 'eab-admin', $admin_js, [ 'jquery' ], AAP_VERSION, true );
}
/**
* Render 'adblocker checkbox'
*
* @return void
*/
public function render_settings_activate_extended(): void {
$method = empty( $this->options['method'] ) ? 'nothing' : $this->options['method'];
$redirect_url = $this->options['redirect']['url'] ?? '';
$abf_enabled = $this->options['use-adblocker'] ?? null;
include_once __DIR__ . '/views/setting-activate-extended.php';
}
/**
* Render content box
*
* @return void
*/
public function render_settings_overlay_content(): void {
$content = $this->options['overlay']['content'] ?? '';
$args = [
'textarea_name' => esc_attr( ADVADS_SETTINGS_ADBLOCKER ) . '[overlay][content]',
'textarea_rows' => 16,
'drag_drop_upload' => true,
];
include_once __DIR__ . '/views/setting-overlay-content.php';
}
/**
* Render 'overlay again and dismiss button'
*
* @return void
*/
public function render_settings_dismissible(): void {
$hide_checked = $this->options['overlay']['hide_dismiss'] ?? 0;
$button_text = $this->options['overlay']['dismiss_text'] ?? '';
$option_time_freq = 'time_frequency';
$time_freq = $this->options['overlay'][ $option_time_freq ] ?? 'everytime';
$option_dismiss_style = 'dismiss_style';
$dismiss_style = $this->options['overlay'][ $option_dismiss_style ] ?? '';
include_once __DIR__ . '/views/setting-dismissible.php';
}
/**
* Render 'styling for dismiss button, container, background'
*
* @return void
*/
public function render_settings_styling(): void {
$option_container = 'container_style';
$option_background = 'background_style';
$container_style = $this->options['overlay'][ $option_container ] ?? '';
$background_style = $this->options['overlay'][ $option_background ] ?? '';
include_once __DIR__ . '/views/setting-styling.php';
}
/**
* Render 'exclude settings'
*
* @return void
*/
public function render_settings_exclude(): void {
global $wp_roles;
$roles = $wp_roles->get_names();
$option_exclude = 'exclude';
$exclude = isset( $this->options[ $option_exclude ] ) ? Advanced_Ads_Utils::maybe_translate_cap_to_role( $this->options[ $option_exclude ] ) : [];
include_once __DIR__ . '/views/setting-exclude.php';
}
}

View File

@@ -0,0 +1,8 @@
<?php
/**
* Initialize class.
*
* @package AdvancedAds\Pro
*/
new Advanced_Ads_Pro_Module_Extended_Adblocker_Admin();

View File

@@ -0,0 +1,95 @@
.advads-modal {
display: none;
position: fixed;
z-index: -1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(34, 34, 34, 0.4);
opacity: 0;
pointer-events: none;
-webkit-transition: opacity 400ms ease-in;
-moz-transition: opacity 400ms ease-in;
transition: opacity 400ms ease-in;
}
.advads-modal:target,
.advads-modal[open] {
display: flex;
align-items: center;
opacity: 1;
pointer-events: auto;
z-index: 9999;
}
dialog.advads-modal {
padding: 0;
border: 0;
margin: 0;
max-width: 100vw;
max-height: 100vh;
}
.advads-modal-content {
background-color: #fff;
border: 3px solid #20802d;
border-radius: 10px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
box-sizing: border-box;
margin: calc(5vh + var(--wp-admin--admin-bar--height, 0px)) auto 5vh;
width: 50em;
max-width: 50em;
max-height: calc(90vh - var(--wp-admin--admin-bar--height, 0px));
height: auto;
animation-name: advads-modal-animatetop;
animation-duration: 0.4s;
position: relative;
padding: 40px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.advads-modal-body {
padding: 16px 16px;
}
.close-wrapper {
display: flex;
justify-content: center;
margin: 1em 0;
}
button.advads-modal-close-action {
padding: 10px 20px;
border-radius: 3px;
font-weight: bold;
text-decoration: none;
cursor: pointer;
outline: inherit;
color: #fff;
border: 1px solid #0474a2;
margin: auto 40px;
background-color: #20802d;
}
@keyframes advads-modal-animatetop {
from {
top: -300px;
opacity: 0
}
to {
top: 0;
opacity: 1
}
}
.advads-ui-autocomplete.ui-front {
z-index: 10000;
}
@media (min-width: 60em) {
.advads-modal-content {
margin: 5% auto;
}
}

View File

@@ -0,0 +1,40 @@
(function ($) {
const overlayNotice = $('.advads-eab-overlay-notice');
const divOverlay = $('#advanced-ads-adblocker-overlay-options');
const divRedirect = $('#advanced-ads-adblocker-redirect-options');
const divExclude = $('#advanced-ads-adblocker-option-exclude');
const adblockerFixEnabled = $('#advanced-ads-use-adblocker').prop(
'checked'
);
$('.advanced-ads-adblocker-eab-method').on('change', function () {
overlayNotice.hide();
switch (this.value) {
case 'nothing':
divOverlay.hide();
divRedirect.hide();
divExclude.hide();
break;
case 'overlay':
divOverlay.show();
divRedirect.hide();
divExclude.show();
// eslint-disable-next-line no-unused-expressions
!adblockerFixEnabled && overlayNotice.show();
break;
case 'redirect':
divOverlay.hide();
divRedirect.show();
divExclude.show();
break;
}
});
const dismissButton = $('#advanced-ads-adblocker-dismiss-button-input');
dismissButton.on('change', function () {
$('#advanced-ads-adblocker-dismiss-options').toggle(!this.checked);
});
dismissButton.change();
})(jQuery);

View File

@@ -0,0 +1 @@
!function(e){const a=e(".advads-eab-overlay-notice"),d=e("#advanced-ads-adblocker-overlay-options"),o=e("#advanced-ads-adblocker-redirect-options"),c=e("#advanced-ads-adblocker-option-exclude"),s=e("#advanced-ads-use-adblocker").prop("checked");e(".advanced-ads-adblocker-eab-method").on("change",(function(){switch(a.hide(),this.value){case"nothing":d.hide(),o.hide(),c.hide();break;case"overlay":d.show(),o.hide(),c.show(),!s&&a.show();break;case"redirect":d.hide(),o.show(),c.show()}}));const n=e("#advanced-ads-adblocker-dismiss-button-input");n.on("change",(function(){e("#advanced-ads-adblocker-dismiss-options").toggle(!this.checked)})),n.change()}(jQuery);

View File

@@ -0,0 +1,240 @@
<?php // phpcs:disable WordPress.Files.FileName.InvalidClassFileName
use AdvancedAds\Modal;
use AdvancedAds\Framework\Utilities\Params;
/**
* Adblocker
*/
class Advanced_Ads_Pro_Module_Extended_Adblocker {
/**
* Setting options
*
* @var array
*/
private $options = [];
/**
* Overlay time frequency
*
* @var string
*/
private $show_frequency;
/**
* Overlay last displayed: cookie name
*
* @var string
*/
private const COOKIE_NAME = AA_PRO_SLUG . '-overlay-last-display';
/**
* Constructor
*
* @return void
*/
public function __construct() {
$this->options = Advanced_Ads::get_instance()->get_adblocker_options();
$this->show_frequency = $this->options['overlay']['time_frequency'] ?? 'everytime';
$this->options['method'] = $this->options['method'] ?? 'nothing';
add_action( 'init', [ $this, 'print_extended_adblocker' ], 99 );
}
/**
* Print the appropriate code on frontend.
*
* @return void
*/
public function print_extended_adblocker(): void {
if ( ! $this->prechecks() ) {
return;
}
$method_name = '';
if ( 'overlay' === $this->options['method'] ) {
wp_enqueue_style( 'eab-modal', plugin_dir_url( __FILE__ ) . 'assets/css/modal.css', [], AAP_VERSION );
if ( $this->should_show_overlay() ) {
$this->set_last_display_time();
$method_name = 'print_overlay';
}
} elseif ( 'redirect' === $this->options['method'] ) {
$method_name = 'print_redirect';
}
if ( method_exists( $this, $method_name ) ) {
add_action( 'wp_footer', [ $this, $method_name ], 99 );
}
}
/**
* Overlay logic
*
* @return void
*/
public function print_overlay(): void {
$button_text = null;
// Show dismiss only when hide button is unchecked.
if ( ! isset( $this->options['overlay']['hide_dismiss'] ) ) {
$button_text = __( 'Dismiss', 'advanced-ads-pro' );
if ( isset( $this->options['overlay']['dismiss_text'] ) && '' !== $this->options['overlay']['dismiss_text'] ) {
$button_text = $this->options['overlay']['dismiss_text'];
}
}
Modal::create(
[
'modal_slug' => 'extended-adblocker',
'modal_content' => $this->options['overlay']['content'],
'close_action' => $button_text,
'template' => plugin_dir_path( __FILE__ ) . 'views/modal.php',
'dismiss_button_styling' => $this->options['overlay']['dismiss_style'],
'container_styling' => $this->options['overlay']['container_style'],
'background_styling' => $this->options['overlay']['background_style'],
]
);
?>
<script>
jQuery(function() {
window.advanced_ads_check_adblocker( function ( is_enabled ) {
if ( is_enabled ) {
document.getElementById('modal-extended-adblocker').showModal();
}
} );
});
</script>
<?php
}
/**
* Redirect logic
*
* @return void
*/
public function print_redirect(): void {
$url = $this->options['redirect']['url'] ?? '';
$current_url = ( is_ssl() ? 'https://' : 'http://' ) .
Params::server( 'HTTP_HOST' ) .
Params::server( 'REQUEST_URI' );
if ( ! empty( $url ) && ! $this->compare_urls( $url, $current_url ) ) {
?>
<script>
jQuery(function() {
window.advanced_ads_check_adblocker( function ( is_enabled ) {
if ( is_enabled ) {
window.location.href = '<?php echo esc_html( $url ); ?>';
}
} );
});
</script>
<?php
}
}
/**
* Conditions which has to pass to go ahead
*
* @return bool
*/
private function prechecks(): bool {
/**
* Don't run:
* - on AMP
* - when method is set to 'nothing'
*/
if ( advads_is_amp() || 'nothing' === $this->options['method'] ) {
return false;
}
// check if exclude matches with current user.
$hide_for_roles = isset( $this->options['exclude'] )
? Advanced_Ads_Utils::maybe_translate_cap_to_role( $this->options['exclude'] )
: [];
$user = wp_get_current_user();
if ( is_user_logged_in() &&
$hide_for_roles &&
is_array( $user->roles ) &&
array_intersect( $hide_for_roles, $user->roles ) ) {
return false;
}
return true;
}
/**
* Overlay Helpers
*
* @return int
*/
private function get_last_display_time(): int {
return Params::cookie( self::COOKIE_NAME, 0 );
}
/**
* Overlay Helpers
*
* @return void
*/
private function set_last_display_time(): void {
$expire_time = time() + $this->get_interval_from_frequency();
setcookie( self::COOKIE_NAME, time(), $expire_time, '/' );
}
/**
* Overlay Helpers
*
* @return bool
*/
private function should_show_overlay(): bool {
switch ( $this->show_frequency ) {
case 'everytime':
return true;
case 'never':
return false;
default:
$last_display_time = $this->get_last_display_time();
$interval = $this->get_interval_from_frequency( $this->show_frequency );
return time() >= $last_display_time + $interval;
}
}
/**
* Overlay Helpers
*
* @return int
*/
private function get_interval_from_frequency(): int {
// Map frequency strings to seconds.
$intervals = [
'everytime' => 0,
'hour' => 3600,
'day' => 86400,
'week' => 604800,
'month' => 2592000, // 30 days.
];
return $intervals[ $this->show_frequency ];
}
/**
* Compare URLs
*
* @param string $url1 URL in database.
* @param string $url2 current page url.
*
* @return bool
*/
private function compare_urls( $url1, $url2 ): bool {
$url1 = wp_parse_url( $url1 );
$url2 = wp_parse_url( $url2 );
return $url1['path'] === $url2['path'];
}
}

View File

@@ -0,0 +1,10 @@
<?php
/**
* Initialize class.
*
* @package AdvancedAds\Pro
*/
if ( ! is_admin() ) {
new Advanced_Ads_Pro_Module_Extended_Adblocker();
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Extended Adblocker Popup Modal
*
* @package AdvancedAds\Pro
*
* @var string $modal_slug Unique slug that can be addressed by a link or button.
* @var string $modal_content The modal content. May contain HTML.
* @var string $close_action Adds close button.
* @var string $dismiss_button_styling Dismiss button styling.
* @var string $container_styling Container styling.
* @var string $background_styling Background styling.
*/
?>
<dialog id="modal-<?php echo esc_attr( $modal_slug ); ?>"
class="advads-modal"
data-modal-id="<?php echo esc_attr( $modal_slug ); ?>"
autofocus
style="<?php echo esc_attr( $background_styling ?? '' ); ?>">
<div class="advads-modal-content" style="<?php echo esc_attr( $container_styling ?? '' ); ?>">
<div class="advads-modal-body">
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- modal content may contain any kind of custom html
echo $modal_content;
?>
<?php if ( $close_action ) : ?>
<div class="close-wrapper">
<button class="advads-modal-close-action"
onclick="document.getElementById('modal-<?php echo esc_attr( $modal_slug ); ?>').close()"
style="<?php echo esc_attr( $dismiss_button_styling ?? '' ); ?>">
<?php echo esc_html( $close_action ); ?>
</button>
</div>
<?php endif; ?>
</div>
</div>
</dialog>

View File

@@ -0,0 +1,81 @@
<?php
/**
* Activate adblocker.
*
* @package Advanced_Ads_Pro\Module
*
* @var string $option_name array index name
* @var string $option_exclude array index name
* @var boolean $checked True, when the option is checked.
* @var boolean $exclude_checked True, when the option is checked.
* @var boolean $abf_enabled True, when the adblocker disguise is enabled.
*/
?>
<label class="advads-eadblocker-radio-button">
<input class="advanced-ads-adblocker-eab-method" name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . '[method]' ); ?>" type="radio" value="nothing"<?php checked( $method, 'nothing' ); ?> />
<?php esc_html_e( 'No additional actions', 'advanced-ads-pro' ); ?>
</label>
<label class="advads-eadblocker-radio-button">
<input class="advanced-ads-adblocker-eab-method" name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . '[method]' ); ?>" type="radio" value="overlay"<?php checked( $method, 'overlay' ); ?> />
<?php esc_html_e( 'Overlay', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip" style="position: fixed; left: 628px; top: 332px;">
<?php
esc_html_e(
'Show a custom overlay to users with an ad blocker enabled, prompting them to turn it off on your website.',
'advanced-ads-pro'
);
?>
</span>
</span>
</label>
<label class="advads-eadblocker-radio-button">
<input class="advanced-ads-adblocker-eab-method" name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . '[method]' ); ?>" type="radio" value="redirect"<?php checked( $method, 'redirect' ); ?> />
<?php esc_html_e( 'Redirect', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip" style="position: fixed; left: 628px; top: 332px;">
<?php
esc_html_e(
'Automatically redirect users with ad blockers enabled to an internal page. Content access is granted after turning off the ad blocker.',
'advanced-ads-pro'
);
?>
</span>
</span>
</label>
<p class="description advads-eab-overlay-notice">
<?php esc_html_e( 'Activate the ad blocker disguise above to display the overlay.', 'advanced-ads-pro' ); ?>
</p>
<div id="advanced-ads-adblocker-overlay-options" <?php echo 'overlay' === $method ? '' : 'style="display: none;"'; ?>>
<?php
$this->render_settings_overlay_content();
$this->render_settings_dismissible();
$this->render_settings_styling();
?>
</div>
<div id="advanced-ads-adblocker-redirect-options" <?php echo 'redirect' === $method ? '' : 'style="display: none;"'; ?>>
<h4>
<?php esc_html_e( 'Redirect URL', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php
esc_html_e(
'Enter a specific page on your domain to which users with activated AdBlocker should be automatically redirected.',
'advanced-ads-pro'
);
?>
</span>
</span>
</h4>
<input class="width-100" type="text" name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . '[redirect][url]' ); ?>" value="<?php echo esc_attr( $redirect_url ); ?>" />
</div>
<div id="advanced-ads-adblocker-option-exclude" <?php echo ( 'redirect' === $method || 'overlay' === $method ) ? '' : 'style="display: none;"'; ?>>
<?php $this->render_settings_exclude(); ?>
</div>

View File

@@ -0,0 +1,96 @@
<?php
/**
* Ad Blocker overlay options 'overlay again and dismiss button'
*
* @package Advanced_Ads_Pro\Module
*
* @var string $option_time_freq The array index name for the overlay timing option.
* @var string $button_text The option value for the dismiss button text.
* @var string $time_freq The option value for the overlay timing.
* @var boolean $hide_checked True, when the hide dismiss button option is checked.
* @var string $option_dismiss_style The array index name for the dismiss button styling option.
* @var string $dismiss_style The CSS value for the dismiss button styling.
*/
?>
<h4>
<?php esc_html_e( 'When to show overlay again?', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Set the timing for the overlay to reappear after being dismissed.', 'advanced-ads-pro' ); ?>
</span>
</span>
</h4>
<div class="advads-settings-margin">
<select name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . "[overlay][$option_time_freq]" ); ?>">
<option value="everytime" <?php selected( $time_freq, 'everytime' ); ?>>
<?php esc_html_e( 'Everytime', 'advanced-ads-pro' ); ?>
</option>
<option value="hour" <?php selected( $time_freq, 'hour' ); ?>>
<?php esc_html_e( '1 hour', 'advanced-ads-pro' ); ?>
</option>
<option value="day" <?php selected( $time_freq, 'day' ); ?>>
<?php esc_html_e( '1 day', 'advanced-ads-pro' ); ?>
</option>
<option value="week" <?php selected( $time_freq, 'week' ); ?>>
<?php esc_html_e( '1 week', 'advanced-ads-pro' ); ?>
</option>
<option value="month" <?php selected( $time_freq, 'month' ); ?>>
<?php esc_html_e( '1 month', 'advanced-ads-pro' ); ?>
</option>
<option value="never" <?php selected( $time_freq, 'never' ); ?>>
<?php esc_html_e( 'Never', 'advanced-ads-pro' ); ?>
</option>
</select>
</div>
<h4><?php esc_html_e( 'Dismiss button', 'advanced-ads-pro' ); ?></h4>
<input id="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER ); ?>-dismiss-button-input"
type="checkbox"
value="1"
name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . '[overlay][hide_dismiss]' ); ?>"
<?php checked( $hide_checked, 1, true ); ?>>
<label for="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER ); ?>-dismiss-button-input">
<?php esc_html_e( 'Hide dismiss button', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Disabling the dismiss button significantly limits site interaction.', 'advanced-ads-pro' ); ?>
</span>
</span>
</label>
<div id="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER ); ?>-dismiss-options">
<h4>
<?php esc_html_e( 'Dismiss button text', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Enter the text that you want to appear on the dismiss button.', 'advanced-ads-pro' ); ?>
</span>
</span>
</h4>
<input type="text"
name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . '[overlay][dismiss_text]' ); ?>"
value="<?php echo esc_attr( $button_text ); ?>"
placeholder="<?php esc_html_e( 'Dismiss', 'advanced-ads-pro' ); ?>" >
<?php if ( ! empty( $button_text ) ) : ?>
<p class="description"><?php esc_html_e( 'Empty and save to revert to defaults.', 'advanced-ads-pro' ); ?></p>
<?php endif; ?>
<h4>
<?php esc_html_e( 'Dismiss button styling', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Insert CSS to customize the dismiss button layout.', 'advanced-ads-pro' ); ?>
</span>
</span>
</h4>
<input class="width-100"
type="text"
name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . "[overlay][$option_dismiss_style]" ); ?>"
value="<?php echo esc_attr( $dismiss_style ); ?>"
placeholder="e.g. background-color: black; border-radius: 20px;">
<?php if ( ! empty( $dismiss_style ) ) : ?>
<p class="description"><?php esc_html_e( 'Empty and save to revert to defaults.', 'advanced-ads-pro' ); ?></p>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,35 @@
<?php
/**
* Ad Blocker options 'exclude'
*
* @package Advanced_Ads_Pro\Module
* @var string $option_exclude array index name
* @var array $exclude exclude option value
*/
?>
<h4>
<?php esc_html_e( 'Exclude', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Choose which user roles to exclude from this ad blocker countermeasure.', 'advanced-ads-pro' ); ?>
</span>
</span>
</h4>
<div class="advads-settings-checkbox-inline">
<?php
foreach ( $roles as $_role => $_display_name ) :
$checked = in_array( $_role, $exclude, true );
?>
<label>
<input type="checkbox" value="<?php echo esc_attr( $_role ); ?>"
name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . "[$option_exclude][]" ); ?>"
<?php checked( $checked, true ); ?>>
<?php echo esc_html( $_display_name ); ?>
</label>
<?php
endforeach;
?>
</div>

View File

@@ -0,0 +1,12 @@
<?php
/**
* Ad Blocker overlay options 'dismiss button'
*
* @package Advanced_Ads_Pro\Module
* @var string $option_name array index name
* @var boolean $checked True, when the option is checked.
*/
?>
<h4><?php esc_html_e( 'Content', 'advanced-ads-pro' ); ?></h4>
<?php wp_editor( $content, 'adblocker_overlay_content_editor', $args ); ?>

View File

@@ -0,0 +1,47 @@
<?php
/**
* Ad Blocker overlay options 'stylings'
*
* @package Advanced_Ads_Pro\Module
* @var string $option_dismiss array index name
* @var string $option_container array index name
* @var string $option_background array index name
* @var string $container_style css
* @var string $background_style css
*/
?>
<h4>
<?php esc_html_e( 'Container styling', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Insert CSS to customize the overlay container layout.', 'advanced-ads-pro' ); ?>
</span>
</span>
</h4>
<input class="width-100"
type="text"
name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . "[overlay][$option_container]" ); ?>"
value="<?php echo esc_attr( $container_style ); ?>"
placeholder="e.g. background-color: salmon; border-radius: 10px; padding: 20px;">
<?php if ( ! empty( $container_style ) ) : ?>
<p class="description"><?php esc_html_e( 'Empty and save to revert to defaults.', 'advanced-ads-pro' ); ?></p>
<?php endif; ?>
<h4>
<?php esc_html_e( 'Background styling', 'advanced-ads-pro' ); ?>
<span class="advads-help">
<span class="advads-tooltip">
<?php esc_html_e( 'Insert CSS to customize the background of the overlay.', 'advanced-ads-pro' ); ?>
</span>
</span>
</h4>
<input class="width-100"
type="text"
name="<?php echo esc_attr( ADVADS_SETTINGS_ADBLOCKER . "[overlay][$option_background]" ); ?>"
value="<?php echo esc_attr( $background_style ); ?>"
placeholder="e.g. background-color: rgba(0, 0, 0, 0.9);">
<?php if ( ! empty( $background_style ) ) : ?>
<p class="description"><?php esc_html_e( 'Empty and save to revert to defaults.', 'advanced-ads-pro' ); ?></p>
<?php endif; ?>

View File

@@ -0,0 +1,2 @@
<?php
new Advanced_Ads_Pro_AdSense_Public;

View File

@@ -0,0 +1,29 @@
<?php
/**
* Public AdSense functionality.
*/
class Advanced_Ads_Pro_AdSense_Public {
/**
* Constructor.
*/
public function __construct() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( ! empty( $options['cfp']['enabled'] ) ) {
add_filter( 'advanced-ads-gadsense-page-level-code', [ $this, 'overwrite_page_level_code' ], 10, 2 );
}
}
/**
* Overwrite the page-level code of the base plugin.
*
* @param string $code Existing code.
* @param array $parameters Parameters of the AdSense code.
* @return string $code New code.
*/
public function overwrite_page_level_code( $code, $parameters ) {
ob_start();
require_once __DIR__ . '/views/page-level.php';
return ob_get_clean();
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Output auto ads enabled code in head.
*
* This template is a drop-in replacement for the template of the base plugin.
*
* @var array $parameters {
* Parameters of the AdSense code.
*
* @type bool $privacy_enabled Whether to wait for user consent.
* @type bool $npa_enabled Whether to show non-personalized ads.
* @type string $client_id The Google AdSense client ID.
* @type bool $top_anchor AdSense anchor ad on top of pages.
* @type string $top_anchor_code The code for top anchor ads.
* @type string $script_src AdSense script url.
* }
*
*/
?><script>
(function () {
var scriptDone = false;
document.addEventListener( 'advanced_ads_privacy', function ( event ) {
if (
( event.detail.state !== 'accepted' && event.detail.state !== 'not_needed' && ! advads.privacy.is_adsense_npa_enabled() )
|| scriptDone
|| advads.get_cookie( 'advads_pro_cfp_ban' )
) {
return;
}
// Google adsense script can only be added once.
scriptDone = true;
var script = document.createElement( 'script' ),
first = document.getElementsByTagName( 'script' )[0];
script.async = true;
script.src = '<?php echo esc_url( $parameters['script_src'] ); ?>';
<?php
if ( $parameters['top_anchor'] ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- relevant user input has already been escaped.
echo $parameters['top_anchor_code'];
} else {
printf( 'script.dataset.adClient = "%s";', esc_attr( $parameters['client_id'] ) );
}
?>
first.parentNode.insertBefore( script, first );
} );
} )();
</script>

View File

@@ -0,0 +1,179 @@
<?php // phpcs:ignoreFile
/**
* Manage backend-facing logic for GamiPress integration
*/
class Advanced_Ads_Pro_Module_GamiPress_Admin {
/**
* Array of GamiPress visitor conditions.
*
* @var array
*/
private $conditions;
/**
* constructor.
*/
public function __construct() {
add_filter( 'advanced-ads-visitor-conditions', [ $this, 'visitor_conditions' ] );
$this->conditions = [
'gamipress_points' => [
'label' => __( 'GamiPress Points', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on GamiPress user points.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'points_metabox' ],
'check' => [ 'Advanced_Ads_Pro_Module_GamiPress', 'check_points_visitor_condition' ],
],
'gamipress_rank' => [
'label' => __( 'GamiPress Rank', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on GamiPress user ranks.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'achievement_rank_metabox' ],
'check' => [ 'Advanced_Ads_Pro_Module_GamiPress', 'check_rank_visitor_condition' ],
],
'gamipress_achievement' => [
'label' => __( 'GamiPress Achievement', 'advanced-ads-pro' ),
'description' => __( 'Display ads based on GamiPress user achievements.', 'advanced-ads-pro' ),
'metabox' => [ $this, 'achievement_rank_metabox' ],
'check' => [ 'Advanced_Ads_Pro_Module_GamiPress', 'check_achievement_visitor_condition' ],
],
];
}
/**
* Add visitor condition for GamiPress fields
*
* @param array $conditions array of registered visitor conditions.
*
* @return array
*/
public function visitor_conditions( $conditions ) {
return array_merge( $conditions, $this->conditions );
}
/**
* Render visitor conditions for achievements and ranks.
*
* @param array $options condition options.
* @param int $index index of the option.
* @param string $form_name name of the form.
*/
public function achievement_rank_metabox( $options, $index, $form_name ) {
$type = $options['type'];
$condition = $this->conditions[ $type ];
$name = Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions::get_form_name_with_index( $form_name, $index );
$operator = isset( $options['operator'] ) ? $options['operator'] : 'is';
$selected_value = isset( $options['value'] ) ? $options['value'] : 0;
$values = $this->get_possible_values( $this->get_possible_types( $options['type'] ) );
if ( $type === 'gamipress_achievement' ) {
include __DIR__ . '/views/visitor-condition-achievement.php';
} else {
$values = $this->order_ranks_by_priority( $values );
include __DIR__ . '/views/visitor-condition-rank.php';
}
}
/**
* Render visitor conditions for points.
*
* @param array $options condition options.
* @param int $index index of the option.
* @param string $form_name name of the form.
*/
public function points_metabox( $options, $index, $form_name ) {
$type = $options['type'];
$condition = $this->conditions[ $type ];
$name = Advanced_Ads_Pro_Module_Advanced_Visitor_Conditions::get_form_name_with_index( $form_name, $index );
$operator = isset( $options['operator'] ) ? $options['operator'] : 'is';
$point_ids = $this->get_posts( 'points-type' );
$points = array_combine( $point_ids, array_map( static function( $post_id ) {
return get_post_meta( $post_id, '_gamipress_plural_name', true );
}, $point_ids ) );
$selected_points_type = isset( $options['points'] ) ? $options['points'] : 0;
$value = isset( $options['value'] ) ? $options['value'] : 0;
include __DIR__ . '/views/visitor-condition-points.php';
}
/**
* Get an array of achievements/rank for each achievement/rank type.
*
* @param int[] $types Post ids for type post types.
*
* @return array
*/
private function get_possible_values( $types ) {
return array_filter( array_combine(
// get the type name as optgroup title
array_map( static function( $post_id ) {
return get_the_title( $post_id );
}, $types ),
// get the actual values as $post_id => name
array_map( function( $post_id ) {
$children = $this->get_posts( get_post_field( 'post_name', $post_id ) );
return array_combine( $children, array_map( static function( $post_id ) {
return get_the_title( $post_id );
}, $children ) );
}, $types )
) );
}
/**
* Get array of post ids for $post_type.
*
* @param string $post_type The post type to search for.
*
* @return int[]
*/
private function get_posts( $post_type ) {
return ( new WP_Query( [
'post_type' => $post_type,
'posts_per_page' => -1,
'post_status' => 'publish',
'fields' => 'ids',
] ) )->posts;
}
/**
* Get an array of post ids for achievement/rank types.
*
* @param string $type Post type name.
*
* @return int[]
*/
private function get_possible_types( $type ) {
if ( $type === 'gamipress_achievement' ) {
return $this->get_posts( 'achievement-type' );
}
if ( $type === 'gamipress_rank' ) {
return $this->get_posts( 'rank-type' );
}
return [];
}
/**
* As ranks have priorities, we want to sort them based on it.
*
* @param array $ranks Array of ranks in form [int $post_id => string $post_title].
*
* @return array
*/
private function order_ranks_by_priority( array $ranks ) {
foreach ( $ranks as &$sub_ranks ) {
uksort( $sub_ranks, static function( $a, $b ) {
$a_priority = gamipress_get_rank_priority( $a );
$b_priority = gamipress_get_rank_priority( $b );
if ( $a_priority === $b_priority ) {
return 0;
}
return ( $a_priority < $b_priority ) ? -1 : 1;
} );
}
return $ranks;
}
}

View File

@@ -0,0 +1,7 @@
<?php
if ( ! class_exists( 'GamiPress' ) ) {
return;
}
new Advanced_Ads_Pro_Module_GamiPress_Admin();

View File

@@ -0,0 +1,72 @@
<?php
/**
* Callbacks for GamiPress visitor conditions.
*/
class Advanced_Ads_Pro_Module_GamiPress {
/**
* Check if current user meets points visitor condition.
*
* @param array $condition Array of visitor condition options.
*
* @return bool
*/
public static function check_points_visitor_condition( $condition ) {
$points = gamipress_get_user_points(
get_current_user_id(),
// dynamically get the post_name from the WP_Post::ID, post_name can change, while ID is immutable.
get_post_field( 'post_name', (int) $condition['points'] )
);
$compare = (int) $condition['value'];
switch ( $condition['operator'] ) {
case 'is_higher':
return $points >= $compare;
case 'is_lower':
return $points <= $compare;
case 'is_equal':
default:
return $points === $compare;
}
}
/**
* Check if current user meets rank visitor condition.
*
* @param array $condition Array of visitor condition options.
*
* @return bool
*/
public static function check_rank_visitor_condition( $condition ) {
$condition_rank = gamipress_get_rank_priority( (int) $condition['value'] );
$user_rank = gamipress_get_rank_priority( gamipress_get_user_rank(
get_current_user_id(),
gamipress_get_post_type( (int) $condition['value'] )
) );
switch ( $condition['operator'] ) {
case 'is_higher':
return $user_rank >= $condition_rank;
case 'is_lower':
return $user_rank <= $condition_rank;
case 'is_equal':
default:
return $user_rank === $condition_rank;
}
}
/**
* Check if current user meets achievement visitor condition.
*
* @param array $condition Array of visitor condition options.
*
* @return bool
*/
public static function check_achievement_visitor_condition( $condition ) {
$achieved = gamipress_has_user_earned_achievement( (int) $condition['value'], get_current_user_id() );
if ( $condition['operator'] === 'is_not' ) {
return ! $achieved;
}
return $achieved;
}
}

View File

@@ -0,0 +1,8 @@
<?php
if ( ! class_exists( 'GamiPress' ) ) {
return;
}
// the callbacks for the visitor conditions are registered in admin.
new Advanced_Ads_Pro_Module_GamiPress_Admin();

View File

@@ -0,0 +1,37 @@
<?php
/**
* View for GamiPress ranks and achievements visitor condition.
*
* @var string $type The visitor condition type.
* @var string $name Unique input name.
* @var string $operator The comparison operator.
* @var array $condition Details for the condition.
* @var array $values GamiPress achievements or ranks.
* @var array $selected_value The currently selected value.
*/
?>
<input type="hidden" name="<?php echo esc_attr( $name ); ?>[type]" value="<?php echo esc_attr( $type ); ?>"/>
<div class="advads-conditions-single">
<select name="<?php echo esc_attr( $name ); ?>[operator]">
<option value="is" <?php selected( 'is', $operator ); ?>><?php esc_attr_e( 'is', 'advanced-ads-pro' ); ?></option>
<option value="is_not" <?php selected( 'is_not', $operator ); ?>><?php esc_attr_e( 'is not', 'advanced-ads-pro' ); ?></option>
</select>
<div class="advads-conditions-select-wrap">
<select name="<?php echo esc_attr( $name ); ?>[value]">
<option value="" disabled <?php selected( 0, $selected_value ); ?>><?php esc_attr_e( 'Choose Achievement Type', 'advanced-ads-pro' ); ?></option>
<?php foreach ( $values as $label => $optgroup ) : ?>
<optgroup label="<?php echo esc_attr( $label ); ?> ">
<?php foreach ( $optgroup as $value => $option ) : ?>
<option value="<?php echo (int) $value; ?>" <?php selected( $value, $selected_value ); ?>><?php echo esc_attr( $option ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
</div>
<p class="description">
<?php echo esc_html( $condition['description'] ); ?>
<a href="https://wpadvancedads.com/manual/gamipress-ads/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-gamipress" class="advads-manual-links" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
</div>

View File

@@ -0,0 +1,44 @@
<?php
/**
* View for GamiPress points visitor condition.
*
* @var string $type The visitor condition type.
* @var string $name Unique input name.
* @var string $operator The comparison operator.
* @var array $condition Details for the condition.
* @var array $points GamiPress points types.
* @var array $selected_points_type The currently selected points type.
*/
?>
<input type="hidden" name="<?php echo esc_attr( $name ); ?>[type]" value="<?php echo esc_attr( $type ); ?>"/>
<div class="advads-conditions-single">
<div class="advads-conditions-select-wrap">
<select name="<?php echo esc_attr( $name ); ?>[points]">
<option value="" disabled <?php selected( 0, $selected_points_type ); ?>><?php esc_attr_e( 'Choose Points Type', 'advanced-ads-pro' ); ?></option>
<?php foreach ( $points as $points_id => $label ) : ?>
<option value="<?php echo (int) $points_id; ?>" <?php selected( $points_id, $selected_points_type ); ?>><?php echo esc_attr( $label ); ?></option>
<?php endforeach; ?>
</select>
</div>
<select name="<?php echo esc_attr( $name ); ?>[operator]">
<option value="is_equal" <?php selected( 'is_equal', $operator ); ?>>
<?php esc_html_e( 'is equal', 'advanced-ads' ); ?>
</option>
<option value="is_higher" <?php selected( 'is_higher', $operator ); ?>>
<?php esc_html_e( 'is higher or equal', 'advanced-ads' ); ?>
</option>
<option value="is_lower" <?php selected( 'is_lower', $operator ); ?>>
<?php esc_html_e( 'is lower or equal', 'advanced-ads' ); ?>
</option>
</select>
<input type="number" name="<?php echo esc_attr( $name ); ?>[value]" value="<?php echo (int) $value; ?>" min="0">
<p class="description">
<?php echo esc_html( $condition['description'] ); ?>
<a href="https://wpadvancedads.com/manual/gamipress-ads/?utm_source=advanced-ads&utm_medium=link&utm_campaign=condition-gamipress" class="advads-manual-link" target="_blank">
<?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?>
</a>
</p>
</div>

Some files were not shown because too many files have changed in this diff Show More