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,546 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Widget;
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Framework\Utilities\Params;
/**
*
* NOTE: can not rely actively on base plugin without prior test for existence (only after plugins_loaded hook)
*/
class Advanced_Ads_Pro_Admin {
/**
* Link to plugin page
*
* @since 1.1
* @const
*/
const PLUGIN_LINK = 'https://wpadvancedads.com/add-ons/advanced-ads-pro/';
/**
* Field name of the user role
*
* @since 1.2.5
* @const
*/
const ROLE_FIELD_NAME = 'advanced-ads-role';
/**
* Advanced Ads user roles array.
*
* @var array
*/
private $roles;
/**
* Initialize the plugin
*
* @since 1.0.0
*/
public function __construct() {
$this->roles = [
'advanced_ads_admin' => __( 'Ad Admin', 'advanced-ads-pro' ),
'advanced_ads_manager' => __( 'Ad Manager', 'advanced-ads-pro' ),
'advanced_ads_user' => __( 'Ad User', 'advanced-ads-pro' ),
'' => __( '--no role--', 'advanced-ads-pro' ),
];
// Add add-on settings to plugin settings page.
add_action( 'advanced-ads-settings-init', [ $this, 'settings_init' ], 9 );
add_filter( 'advanced-ads-setting-tabs', [ $this, 'setting_tabs' ] );
// Add user role selection to users page.
add_action( 'show_user_profile', [ $this, 'add_user_role_fields' ] );
add_action( 'edit_user_profile', [ $this, 'add_user_role_fields' ] );
add_action( 'profile_update', [ $this, 'save_user_role' ] );
// Display warning if advanced visitor conditions are not active.
add_action( 'advanced-ads-visitor-conditions-after', [ $this, 'show_condition_notice' ], 10, 0 );
// Display "once per page" field.
add_action( 'advanced-ads-output-metabox-after', [ $this, 'render_ad_output_options' ] );
// Load admin style sheet.
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_styles' ] );
// Render repeat option for Content placement.
add_action( 'advanced-ads-placement-post-content-position', [ $this, 'render_placement_repeat_option' ], 10, 2 );
add_filter( 'pre_update_option_advanced-ads', [ $this, 'pre_update_advanced_ads_options' ], 10, 2 );
// Show/hide warnings for privacy module based on Pro state.
add_filter( 'advanced-ads-privacy-custom-show-warning', [ $this, 'show_custom_privacy_warning' ] );
add_filter( 'advanced-ads-privacy-tcf-show-warning', '__return_false' );
add_filter( 'advanced-ads-privacy-custom-link-attributes', [ $this, 'privacy_link_attributes' ] );
add_filter( 'advanced-ads-ad-privacy-hide-ignore-consent', [ $this, 'hide_ignore_consent_checkbox' ], 10, 3 );
// Show a warning if cache-busting is enabled, but no placement is used for a widget.
add_action( 'in_widget_form', [ $this, 'show_no_placement_in_widget_warning' ], 10, 3 );
add_action( 'advanced-ads-export-options', [ $this, 'export_options' ] );
// Suggest a text for the WP Privacy Policy
add_action( 'admin_init', [ $this, 'add_privacy_policy_content' ] );
// Trim custom code on save.
add_action( 'advanced-ads-ad-pre-save', [$this, 'trim_custom_code_on_save'], 10, 2 );
}
/**
* Trim whitespaces in custom code when saving an ad
*
* @param Ad $ad the ad.
* @param array $post_data sanitized content of $_POST.
*
* @return void
*/
public function trim_custom_code_on_save( $ad, $post_data ) {
if ( isset( $post_data['custom-code'] ) ) {
$ad->set_prop_temp( 'custom-code', trim( (string) $post_data['custom-code'] ) );
}
}
/**
* Add settings to settings page
*
* @param string $hook settings page hook.
* @since 1.0.0
*/
public function settings_init( $hook ) {
register_setting( Advanced_Ads_Pro::OPTION_KEY, Advanced_Ads_Pro::OPTION_KEY );
/**
* Allow Ad Admin to save pro options.
*
* @param array $settings Array with allowed options.
*
* @return array
*/
add_filter( 'advanced-ads-ad-admin-options', function( $options ) {
$options[] = Advanced_Ads_Pro::OPTION_KEY;
return $options;
} );
// Add new section.
add_settings_section(
Advanced_Ads_Pro::OPTION_KEY . '_modules-enable',
'',
[ $this, 'render_modules_enable' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings'
);
// Add new section.
add_settings_section(
'advanced_ads_pro_settings_section',
'',
[ $this, 'render_other_settings' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings'
);
// Setting for Autoptimize support.
$has_optimizer_installed = Advanced_Ads_Checks::active_autoptimize();
if ( ! $has_optimizer_installed && method_exists( 'Advanced_Ads_Checks', 'active_wp_rocket' ) ) {
$has_optimizer_installed = Advanced_Ads_Checks::active_wp_rocket();
}
if ( $has_optimizer_installed ) {
add_settings_field(
'autoptimize-support',
__( 'Allow optimizers to modify ad codes', 'advanced-ads-pro' ),
[ $this, 'render_settings_autoptimize' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings',
'advanced_ads_pro_settings_section'
);
}
add_settings_field(
'placement-positioning',
__( 'Placement positioning', 'advanced-ads-pro' ),
[ $this, 'render_settings_output_buffering' ],
Advanced_Ads_Pro::OPTION_KEY . '-settings',
'advanced_ads_pro_settings_section'
);
add_settings_field(
'disable-by-post-types',
__( 'Disable ads for post types', 'advanced-ads-pro' ),
[ $this, 'render_settings_disable_post_types' ],
$hook,
'advanced_ads_setting_section_disable_ads'
);
}
/**
* Copy settings from `general` tab in order to prevent it from being cleaned
* when Pro is deactivated.
*
* @param mixed $options Advanced Ads options.
* @return mixed options
*/
public function pre_update_advanced_ads_options( $options ) {
$pro = Advanced_Ads_Pro::get_instance()->get_options();
if ( isset( $options['pro']['general']['disable-by-post-types'] ) && is_array( $options['pro']['general']['disable-by-post-types'] ) ) {
$pro['general']['disable-by-post-types'] = $options['pro']['general']['disable-by-post-types'];
} else {
$pro['general']['disable-by-post-types'] = [];
}
Advanced_Ads_Pro::get_instance()->update_options( $pro );
return $options;
}
/**
* Render content of module enable option
*/
public function render_modules_enable() {
}
/**
* Render additional pro settings
*
* @since 1.1
*/
public function render_other_settings() {
// Save options when the user is on the "Pro" tab.
$selected = $this->get_disable_by_post_type_options();
foreach ( $selected as $item ) { ?>
<input type="hidden" name="<?php echo esc_attr( AAP_SLUG ); ?>[general][disable-by-post-types][]" value="<?php echo esc_html( $item ); ?>">
<?php
}
}
/**
* Render Autoptimize settings field.
*
* @since 1.2.3
*/
public function render_settings_autoptimize() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
$autoptimize_support_disabled = $options['autoptimize-support-disabled'] ?? false;
require AA_PRO_ABSPATH . '/views/setting_autoptimize.php';
}
/**
* Render output buffering settings field.
*/
public function render_settings_output_buffering() {
$placement_positioning = Advanced_Ads_Pro::get_instance()->get_options()['placement-positioning'] === 'js' ? 'js' : 'php';
$allowed_types = [
'post_above_headline',
'custom_position',
];
$allowed_types_names = [];
foreach ( $allowed_types as $allowed_type ) {
$allowed_type = wp_advads_get_placement_type( $allowed_type );
if ( $allowed_type && '' !== $allowed_type->get_title() ) {
$allowed_types_names[] = $allowed_type->get_title();
}
}
require AA_PRO_ABSPATH . '/views/setting-placement-positioning.php';
}
/**
* Render settings to disable ads by post types.
*/
public function render_settings_disable_post_types() {
$selected = $this->get_disable_by_post_type_options();
$post_types = get_post_types(
[
'public' => true,
'publicly_queryable' => true,
],
'objects',
'or'
);
$type_label_counts = array_count_values( wp_list_pluck( $post_types, 'label' ) );
require AA_PRO_ABSPATH . '/views/setting_disable_post_types.php';
}
/**
* Get "Disabled by post type" Pro options.
*/
private function get_disable_by_post_type_options() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( isset( $options['general']['disable-by-post-types'] ) && is_array( $options['general']['disable-by-post-types'] ) ) {
$selected = $options['general']['disable-by-post-types'];
} else {
$selected = [];
}
return $selected;
}
/**
* Add tracking settings tab
*
* @since 1.2.0
* @param array $tabs existing setting tabs.
* @return array $tabs setting tabs with AdSense tab attached.
*/
public function setting_tabs( array $tabs ) {
$tabs['pro'] = [
// TODO abstract string.
'page' => Advanced_Ads_Pro::OPTION_KEY . '-settings',
'group' => Advanced_Ads_Pro::OPTION_KEY,
'tabid' => 'pro',
'title' => 'Pro',
];
return $tabs;
}
/**
* Form field for user role selection
*
* @param array $user user data.
*/
public function add_user_role_fields( $user ) {
if ( ! current_user_can( 'edit_users' ) ) {
return;
}
$role = get_user_meta( $user->ID, self::ROLE_FIELD_NAME, true );
?>
<h3><?php esc_html_e( 'Advanced Ads User Role', 'advanced-ads-pro' ); ?></h3>
<table class="form-table">
<tr>
<th><label for="advads_pro_role"><?php esc_html_e( 'Ad User Role', 'advanced-ads-pro' ); ?></label></th>
<td><select name="<?php echo esc_attr( self::ROLE_FIELD_NAME ); ?>" id="advads_pro_role">
<?php
foreach ( $this->roles as $_slug => $_name ) :
?>
<option value="<?php echo esc_attr( $_slug ); ?>" <?php selected( $role, $_slug ); ?>><?php echo esc_html( $_name ); ?></option>
<?php
endforeach;
?>
</select>
<p class="description"><?php esc_html_e( 'Please note, with the last update, the “Ad Admin“ and “Ad Manager“ roles have the “upload_files“ and the “unfiltered_html“ capabilities.', 'advanced-ads-pro' ); ?></p>
</td>
</tr>
</table>
<?php
}
/**
* Update the user role
*
* @param int $user_id ID of the user.
*/
public function save_user_role( $user_id) {
if (
! array_key_exists( self::ROLE_FIELD_NAME, $_POST )
|| ! current_user_can( 'edit_users' )
|| ! wp_verify_nonce( Params::post( '_wpnonce' ), 'update-user_' . $user_id)
) {
return;
}
// check if this is a valid user role.
$user_role = sanitize_text_field( Params::post( self::ROLE_FIELD_NAME ) );
if ( ! array_key_exists( $user_role, $this->roles ) ) {
return;
}
// Get user object.
$user = new WP_User( $user_id );
// Remove previous role.
$prev_role = get_user_meta( $user_id, self::ROLE_FIELD_NAME, true );
$user->remove_role( $prev_role );
// Save new role as user meta.
update_user_meta( $user_id, self::ROLE_FIELD_NAME, $user_role );
if ( $user_role ) {
// Add role.
$user->add_role( $user_role );
}
}
/**
* Show a notice if advanced visitor conditions are disabled. Maybe some users are looking for it
*/
public function show_condition_notice() {
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( ! isset( $options['advanced-visitor-conditions']['enabled'] ) ) {
echo '<p>' . sprintf(
wp_kses(
/* translators: %s: URL to the settings page */
__( 'Enable the Advanced Visitor Conditions <a href="%s" target="_blank">in the settings</a>.', 'advanced-ads-pro' ),
[
'a' => [
'href' => [],
'target' => [],
],
]
),
esc_url( admin_url( 'admin.php?page=advanced-ads-settings#top#pro' ) )
) . '</p>';
}
}
/**
* Add output options to ad edit page
*
* @param Ad $ad Ad instance.
*/
public function render_ad_output_options( Ad $ad ) {
$once_per_page = $ad->get_prop( 'once_per_page' ) ? 1 : 0;
require AA_PRO_ABSPATH . '/views/setting_output_once.php';
// Get CodeMirror setting for Custom code textarea.
$settings = $this->get_code_editor_settings();
$custom_code = ! empty( $ad->get_prop( 'custom-code' ) ) ? esc_textarea( $ad->get_prop( 'custom-code' ) ) : '';
$privacy_options = Advanced_Ads_Privacy::get_instance()->options();
require AA_PRO_ABSPATH . '/views/setting_custom_code.php';
}
/**
* Render repeat option for Content placement.
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function render_placement_repeat_option( $placement_slug, $placement ) {
$data = $placement->get_data();
$words_between_repeats = ! empty( $data['words_between_repeats'] ) ? absint( $data['words_between_repeats'] ) : 0;
require AA_PRO_ABSPATH . '/views/setting_repeat.php';
}
/**
* Get CodeMirror settings.
*/
public function get_code_editor_settings() {
global $wp_version;
if ( 'advanced_ads' !== get_current_screen()->id
|| defined( 'ADVANCED_ADS_DISABLE_CODE_HIGHLIGHTING' )
|| -1 === version_compare( $wp_version, '4.9' ) ) {
return false;
}
// Enqueue code editor and settings for manipulating HTML.
$settings = wp_enqueue_code_editor( [ 'type' => 'text/html' ] );
if ( ! $settings ) {
$settings = false;
}
return $settings;
}
/**
* Register and enqueue admin-specific style sheet.
*/
public function enqueue_admin_styles() {
wp_enqueue_style( AAP_SLUG . '-admin-styles', AAP_BASE_URL . 'assets/admin.css', [], AAP_VERSION );
}
/**
* Only show privacy warning if cache-busting module not enabled.
*
* @param bool $show Whether to show warning.
*
* @return bool
*/
public function show_custom_privacy_warning( $show ) {
if ( ! $show ) {
return $show;
}
$options = Advanced_Ads_Pro::get_instance()->get_options();
return ! isset( $options['cache-busting']['enabled'] );
}
/**
* Update Link in Privacy settings ot settings page instead of external plugin page.
*
* @return array
*/
public function privacy_link_attributes() {
return [
'href' => esc_url( admin_url( 'admin.php?page=advanced-ads-settings#top#pro' ) ),
];
}
/**
* Show the ignore-consent checkbox if this ad has custom code and type is image or dummy.
* The filter is called `advanced-ads-ad-privacy-hide-ignore-consent`, so the return needs to be !$hide to show.
*
* @param bool $hide Whether to show ignore-consent checkbox.
* @param Ad $ad Ad instance.
*
* @return bool
*/
public function hide_ignore_consent_checkbox( $hide, Ad $ad ) {
if ( ! $hide || ! $ad->is_type( [ 'image', 'dummy' ] ) ) {
return $hide;
}
return empty( Advanced_Ads_Pro::get_instance()->get_custom_code( $ad ) );
}
/**
* Show a warning below the form of Advanced Ads widgets if cache-busting is enabled
* but the widget does not use a placement or "Force passive cache-busting" is enabled
*
* Uses the in_widget_form action hook
*
* @param WP_Widget $widget The widget instance (passed by reference).
* @param null $return Return null if new fields are added.
* @param array $instance An array of the widget's settings.
*/
public function show_no_placement_in_widget_warning( $widget, $return, $instance ) {
// bail if this is not the Advanced Ads widget
if ( ! is_a( $widget, Widget::class ) ) {
return;
}
// bail if cache-busting is not enabled or if Force passive cache-busting is enabled
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( empty( $options['cache-busting']['enabled'] ) || isset( $options['cache-busting']['passive_all'] ) ) {
return;
}
// check item ID and show warning if it is given but does not contain a placement
if ( ! empty( $instance['item_id'] ) && 0 !== strpos( $instance['item_id'], 'placement_' ) ) {
?>
<p class="advads-notice-inline advads-error">
<?php esc_html_e( 'Select a Sidebar placement to enable cache-busting.', 'advanced-ads-pro' ); ?>
<a href="https://wpadvancedads.com/manual/cache-busting/#Cache-Busting_in_Widgets" target="_blank">
<?php esc_html_e( 'Learn more', 'advanced-ads-pro' ); ?>
</a>
</p>
<?php
}
}
/**
* Add Pro options to the list of options to be exported.
*
* @param $options Array of option data keyed by option keys.
* @return $options Array of option data keyed by option keys.
*/
public function export_options( $options ) {
$options[ Advanced_Ads_Pro::OPTION_KEY ] = get_option( Advanced_Ads_Pro::OPTION_KEY );
return $options;
}
/**
* Adds a privacy policy statement under Settings > Privacy > Policy Guide
* which customers can use as a basic templace.
*/
public function add_privacy_policy_content() {
if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
return;
}
ob_start();
include AA_PRO_ABSPATH . 'views/privacy-policy-content.php';
wp_add_privacy_policy_content( 'Advanced Ads Pro', wp_kses_post( wpautop( ob_get_clean(), false ) ) );
}
}

View File

@@ -0,0 +1,626 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Frontend\Stats;
/**
* Class Advanced_Ads_Pro
*/
class Advanced_Ads_Pro {
/**
* Pro options
*
* @var array
*/
protected $options;
/**
* Interal plugin options set by the plugin
*
* @var array (if loaded)
*/
protected $internal_options;
/**
* Option name shared by child modules.
*
* @var string
*/
const OPTION_KEY = 'advanced-ads-pro';
/**
* Name of the frontend script.
*
* @var string
*/
const FRONTEND_SCRIPT_HANDLE = 'advanced-ads-pro/front';
/**
* Instance of Advanced_Ads_Pro
*
* @var Advanced_Ads_Pro
*/
private static $instance;
/**
* Advanced_Ads_Pro constructor.
*/
private function __construct() {
// Setup plugin once base plugin that is initialized at priority `20` is available.
add_action( 'plugins_loaded', [ $this, 'init' ], 30 );
}
/**
* Instance of Advanced_Ads_Pro
*
* @return Advanced_Ads_Pro
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Must not be called before `plugins_loaded` hook.
*/
public function init() {
// TODO: Update routines will be handled by the Advanced Ads Framework. Move this function then accordingly.
$this->plugin_updates();
// Load config and modules.
$options = $this->get_options();
Advanced_Ads_ModuleLoader::loadModules( AAP_PATH . '/modules/', isset( $options['modules'] ) ? $options['modules'] : [] );
// Load admin on demand.
if ( is_admin() ) {
new Advanced_Ads_Pro_Admin();
// Run after the internal Advanced Ads version has been updated by the `Advanced_Ads_Upgrades`, because.
// The `Admin_Notices` can update this version, and the `Advanced_Ads_Upgrades` will not be called.
add_action( 'init', [ $this, 'maybe_update_capabilities' ] );
add_filter( 'advanced-ads-notices', [ $this, 'add_notices' ] );
} else {
// Force advanced js file to be attached.
add_filter( 'advanced-ads-activate-advanced-js', '__return_true' );
// Check autoptimize.
if ( method_exists( 'Advanced_Ads_Checks', 'requires_noptimize_wrapping' ) && Advanced_Ads_Checks::requires_noptimize_wrapping() && ! isset( $options['autoptimize-support-disabled'] ) ) {
add_filter( 'advanced-ads-output-inside-wrapper', [ $this, 'autoptimize_support' ] );
}
}
new Advanced_Ads_Pro_Compatibility();
add_action( 'wp_loaded', [ $this, 'wp_loaded' ] );
add_filter( 'advanced-ads-can-display-ad', [ $this, 'can_display_by_display_limit' ], 10, 3 );
add_filter( 'advanced-ads-ad-output', [ $this, 'add_custom_code' ], 30, 2 );
add_filter( 'advanced-ads-ad-output', [ $this, 'encode_ad_custom_code' ], 20, 2 );
add_filter( 'advanced-ads-placement-content-offsets', [ $this, 'placement_content_offsets' ], 10, 6 );
add_action( 'wp_head', [ $this, 'wp_head' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ] );
}
/**
* Remove shortcodes from the free plugin then replace it with the new ones
*
* @return void
*/
public function wp_loaded() {
$this->remove_shortcodes();
$this->add_shortcodes();
}
/**
* Remove free plugin's shortcodes
*
* @return void
*/
public function remove_shortcodes() {
remove_shortcode( 'the_ad' );
remove_shortcode( 'the_ad_group' );
remove_shortcode( 'the_ad_placement' );
}
/**
* Add new shortcodes
*
* @return void
*/
public function add_shortcodes() {
add_shortcode( 'the_ad', [ $this, 'shortcode_display_ad' ] );
add_shortcode( 'the_ad_group', [ $this, 'shortcode_display_ad_group' ] );
add_shortcode( 'the_ad_placement', [ $this, 'shortcode_display_ad_placement' ] );
}
/**
* Enqueue front end script.
*/
public function wp_enqueue_scripts() {
// Do not enqueue on AMP pages.
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return;
}
wp_enqueue_script(
self::FRONTEND_SCRIPT_HANDLE,
sprintf( '%sassets/js/advanced-ads-pro%s.js', AAP_BASE_URL, defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' ),
[ 'jquery', ADVADS_SLUG . '-advanced-js' ],
AAP_VERSION,
true
);
wp_localize_script(
self::FRONTEND_SCRIPT_HANDLE,
'advanced_ads_cookies',
[
'cookie_path' => COOKIEPATH,
'cookie_domain' => COOKIE_DOMAIN,
]
);
}
/**
* Front end header script.
*/
public function wp_head() {
// Do not enqueue on AMP pages.
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return;
}
?><script type="text/javascript">
var advadsCfpQueue = [];
var advadsCfpAd = function( adID ){
if ( 'undefined' == typeof advadsProCfp ) { advadsCfpQueue.push( adID ) } else { advadsProCfp.addElement( adID ) }
};
</script>
<?php
}
/**
* Return Advanced Ads Pro options
*
* @return array
*/
public function get_options() {
if ( ! isset( $this->options ) ) {
$default_options = [];
$this->options = get_option( self::OPTION_KEY, $default_options );
// Handle previous option key.
if ( $this->options === [] ) {
$old_options = get_option( self::OPTION_KEY . '-modules', false );
if ( $old_options ) {
// Update old options.
$this->update_options( $old_options );
delete_option( self::OPTION_KEY . '-modules' );
}
}
if ( ! isset( $this->options['placement-positioning'] ) ) {
$this->options['placement-positioning'] = 'php';
}
}
return $this->options;
}
/**
* Set a specific option.
*
* @param string $key key to identify the option.
* @param mixed $value value of the option.
*/
public function set_option( $key, $value ) {
$options = $this->get_options();
$options[ $key ] = $value;
$this->update_options( $options );
}
/**
* Update all Advanced Ads Pro options.
*
* @param array $options
*/
public function update_options( array $options ) {
$updated = update_option( self::OPTION_KEY, $options );
if ( $updated ) {
$this->options = $options;
}
}
/**
* Add autoptimize support
*
* @param string $ad_content ad content.
* @return string output that should not be changed by Autoptimize.
*/
public function autoptimize_support( $ad_content = '' ) {
return '<!--noptimize-->' . $ad_content . '<!--/noptimize-->';
}
/**
* Return internal plugin options, these are options set by the plugin
*
* @param bool $set_defaults true if we set default options.
* @return array $options
*/
public function internal_options( $set_defaults = true ) {
if ( ! $set_defaults ) {
return get_option( 'Advanced Ads Pro' . '-internal', [] );
}
if ( ! isset( $this->internal_options ) ) {
$defaults = [
'version' => AAP_VERSION,
];
$this->internal_options = get_option( 'Advanced Ads Pro' . '-internal', [] );
// Save defaults.
if ( $this->internal_options === [] ) {
$this->internal_options = $defaults;
$this->update_internal_options( $this->internal_options );
}
}
return $this->internal_options;
}
/**
* Update internal plugin options
*
* @param array $options new internal options.
*/
public function update_internal_options( array $options ) {
$this->internal_options = $options;
update_option( 'Advanced Ads Pro' . '-internal', $options );
}
/**
* Update capabilities and warn user if needed
*/
public function maybe_update_capabilities() {
$internal_options = $this->internal_options( false );
if ( ! isset( $internal_options['version'] ) ) {
$roles = [ 'advanced_ads_admin', 'advanced_ads_manager' ];
// Add notice if there is at least 1 user with that role.
foreach ( $roles as $role ) {
$users_query = new WP_User_Query(
[
'fields' => 'ID',
'number' => 1,
'role' => $role,
]
);
if ( count( $users_query->get_results() ) ) {
Advanced_Ads_Admin_Notices::get_instance()->add_to_queue( 'pro_changed_caps' );
break;
}
}
$admin_role = get_role( 'advanced_ads_admin' );
if ( $admin_role ) {
$admin_role->add_cap( 'upload_files' );
$admin_role->add_cap( 'unfiltered_html' );
}
$manager_role = get_role( 'advanced_ads_manager' );
if ( $manager_role ) {
$manager_role->add_cap( 'upload_files' );
$manager_role->add_cap( 'unfiltered_html' );
}
// Save new version.
$this->internal_options();
}
}
/**
* Add potential warning to global array of notices.
*
* @param array $notices existing notices.
*
* @return mixed
*/
public function add_notices( $notices ) {
$notices['pro_changed_caps'] = [
'type' => 'update',
'text' => __( 'Please note, the “Ad Admin“ and the “Ad Manager“ roles have the “upload_files“ and the “unfiltered_html“ capabilities', 'advanced-ads-pro' ),
'global' => true,
];
$message = wp_kses(
sprintf(
/* translators: 1 is the opening link to the Advanced Ads website, 2 the closing link */
__(
'We have renamed the Responsive Ads add-on to Advanced Ads AMP Ads. With this change, the Browser Width visitor condition moved from that add-on into Advanced Ads Pro. You can deactivate Advanced Ads AMP Ads if you dont utilize AMP ads or the custom sizes feature for responsive AdSense ad units. %1$sRead more%2$s.',
'advanced-ads-pro'
),
'<a href="https://wpadvancedads.com/responsive-ads-add-on-becomes-amp-ads" target="_blank" class="advads-manual-link">',
'</a>'
),
[
'a' => [
'href' => true,
'target' => true,
'class' => true,
],
]
);
$notices['pro_responsive_migration'] = [
'type' => 'info',
'text' => $message,
'global' => true,
];
return $notices;
}
/**
* Check if the ad can be displayed based on display limit
*
* @param bool $can_display Existing value.
* @param Ad $ad Ad instance.
* @param array $check_options Options to check.
*
* @return bool true if limit is not reached, false otherwise
*/
public function can_display_by_display_limit( $can_display, Ad $ad, $check_options ) {
if ( ! $can_display ) {
return false;
}
if ( empty( $check_options['passive_cache_busting'] ) && $ad->get_prop( 'once_per_page' ) ) {
foreach ( Stats::get()->entities as $item ) {
if ( $item['type'] === 'ad' && absint( $item['id'] ) === $ad->get_id() ) {
return false;
}
}
}
return true;
}
/**
* Get offsets for Content placement.
*
* @param array $offsets Existing Offsets.
* @param array $options Injection options.
* @param array $placement_opts Placement options.
* @param object $xpath DOMXpath object.
* @param array $items Selected items.
* @param object $dom DOMDocument object.
* @return array $offsets New offsets.
*/
public function placement_content_offsets( $offsets, $options, $placement_opts, $xpath = null, $items = null, $dom = null ) {
if ( ! isset( $options['paragraph_count'] ) ) {
return $offsets;
}
if ( isset( $placement_opts['placement']['type'] ) ) {
if ( 'post_content_random' === $placement_opts['placement']['type'] ) {
$max = absint( $options['paragraph_count'] - 1 );
// Skip if have only one paragraph since `wp_rand( 0, 0)` generates large number.
if ( $max > 0 ) {
$rand = wp_rand( 0, $max );
$offsets = [ $rand ];
}
}
if ( 'post_content_middle' === $placement_opts['placement']['type'] ) {
$middle = absint( ( $options['paragraph_count'] - 1 ) / 2 );
$offsets = [ $middle ];
}
}
// "Content" placement, repeat position.
if ( ! empty( $placement_opts['repeat'] ) || ! empty( $options['repeat'] )
&& isset( $options['paragraph_id'] )
&& isset( $options['paragraph_select_from_bottom'] ) ) {
$offsets = [];
for ( $i = $options['paragraph_id'] - 1; $i < $options['paragraph_count']; $i++ ) {
// Select every X number.
if ( ( $i + 1 ) % $options['paragraph_id'] === 0 ) {
$offsets[] = $options['paragraph_select_from_bottom'] ? $options['paragraph_count'] - 1 - $i : $i;
}
}
}
if ( ! empty( $placement_opts['words_between_repeats'] )
&& $xpath && $items && $dom ) {
$options['words_between_repeats'] = absint( $placement_opts['words_between_repeats'] );
$offset_shifter = new Advanced_Ads_Pro_Offset_Shifter( $dom, $xpath, $options );
$offsets = $offset_shifter->calc_offsets( $offsets, $items );
}
return $offsets;
}
/**
* Add custom code after the ad.
*
* Note: this wont work for the Background ad placement. There is a custom solution for that in Advanced_Ads_Pro_Module_Background_Ads:ad_output
*
* @param string $ad_content Ad content.
* @param Ad $ad Ad instance.
* @return string $ad_content Ad content.
*/
public function add_custom_code( $ad_content, Ad $ad ) {
$custom_code = $this->get_custom_code($ad);
if ( empty( $custom_code ) ) {
return $ad_content;
}
$privacy = Advanced_Ads_Privacy::get_instance();
if ( $privacy->is_ad_output_encoded( $ad_content ) ) {
// If the ad_content is already encoded, do not append the custom code in plain text after it.
return $privacy->encode_ad( $this->decode_output( trim( $ad_content ) ) . $custom_code, $ad );
}
return $ad_content . $custom_code;
}
/**
* Retrieve the original ad content from an encoded script tag
*
* @param string $output the encoded output.
*
* @return string
*/
private function decode_output( $output ) {
// Strips the <script ...> and the </script> then base64_decode the remaining characters.
return base64_decode( substr( $output, strpos( $output, '>' ) + 1, -9 ) );
}
/**
* If this ad has custom code, encode the output.
*
* @param string $output The output string.
* @param Ad $ad The ad object.
*
* @return string
*/
public function encode_ad_custom_code( $output, Ad $ad ) {
$privacy = Advanced_Ads_Privacy::get_instance();
if (
// don't encode if AMP.
( function_exists( 'advads_is_amp' ) && advads_is_amp() )
// privacy module is either not enabled, or shows all ads without consent.
|| ( empty( $privacy->options()['enabled'] ) )
// Ad is already encoded.
|| ( ! method_exists( $privacy, 'is_ad_output_encoded' ) || $privacy->is_ad_output_encoded( $output ) )
// Consent is overridden, and this is not an AdSense ad, don't encode it.
|| ( ! $ad->is_type( 'adsense' ) && $ad->get_prop( 'privacy.ignore-consent' ) )
) {
return $output;
}
// If we have custom code, encode the ad.
if ( ! empty( $this->get_custom_code( $ad ) ) ) {
$output = $privacy->encode_ad( $output, $ad );
}
return $output;
}
/**
* Get the custom code for this ad.
*
* @param Ad $ad The ad object.
*
* @return string
*/
public function get_custom_code( Ad $ad ) {
$custom_code = $ad->get_prop( 'custom-code' ) ?? '';
return (string) apply_filters( 'advanced_ads_pro_output_custom_code', $custom_code, $ad );
}
/**
* Enable placement test emails
*/
public static function enable_placement_test_emails() {
$placement_tests = get_option( 'advads-ads-placement-tests', [] );
if ( ! wp_next_scheduled( 'advanced-ads-placement-tests-emails' ) && count($placement_tests) > 0 ) {
// Only schedule if not yet scheduled & tests exists.
wp_schedule_event( time(), 'daily', 'advanced-ads-placement-tests-emails' );
} elseif ( wp_next_scheduled( 'advanced-ads-placement-tests-emails' ) && count($placement_tests) <= 0 ) {
// deactivate if running and tests empty.
self::disable_placement_test_emails();
}
}
/**
* Disable placement test emails
*/
public static function disable_placement_test_emails() {
wp_clear_scheduled_hook( 'advanced-ads-placement-tests-emails' );
}
/**
* Shortcode to include ad in frontend
*
* @param array $atts shortcode attributes.
* @return string content as generated by the shortcode.
*/
public function shortcode_display_ad( $atts ) {
return $this->do_shortcode( $atts, 'render_ad' );
}
/**
* Shortcode to include ad from an ad group in frontend
*
* @param array $atts shortcode attributes.
* @return string content as generated by the shortcode.
*/
public function shortcode_display_ad_group( $atts ) {
return $this->do_shortcode( $atts, 'render_group' );
}
/**
* Shortcode to display content of an ad placement in frontend
*
* @param array $atts shortcode attributes.
* @return string content as generated by the shortcode.
*/
public function shortcode_display_ad_placement( $atts ) {
return $this->do_shortcode( $atts, 'render_placement' );
}
/**
* Create shortcode output.
*
* @param array $atts shortcode attributes.
* @param string $function_name function to be executed by the shortcode.
*
* @return string content as generated by the shortcode.
*/
private function do_shortcode( $atts, $function_name ) {
$blog_id = isset( $atts['blog_id'] ) ? absint( $atts['blog_id'] ) : 0;
if ( $blog_id && $blog_id !== get_current_blog_id() && is_multisite() ) {
// Prevent database error.
if ( ! Advanced_Ads_Pro_Utils::blog_exists( $blog_id ) ) {
return '';
}
if ( is_multisite() ) {
switch_to_blog( $blog_id );
}
// Use the public available function here.
$result = call_user_func( [ wp_advads()->shortcodes, $function_name ], $atts );
if ( is_multisite() ) {
restore_current_blog();
}
return $result;
}
// Use the public available function here.
return call_user_func( [ wp_advads()->shortcodes, $function_name ], $atts );
}
/**
* Plugin update.
*
* @return void
*/
private function plugin_updates(): void {
$pro_options = $this->get_options();
$free_options = Advanced_Ads::get_instance()->options();
if ( isset( $pro_options['responsive-ads'] ) || ! isset( $free_options['responsive-ads'] ) ) {
return;
}
$this->set_option( 'responsive-ads', $free_options['responsive-ads'] );
}
}

View File

@@ -0,0 +1,558 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Calculates new offsets based on amount of words between ads.
*/
class Advanced_Ads_Pro_Offset_Shifter {
/**
* Hold dom.
*
* @var DOMDocument
*/
private $dom = null;
/**
* Hold xpath.
*
* @var DOMXPath
*/
private $xpath = null;
/**
* Hold options.
*
* @var array
*/
private $options = null;
/**
* Default options.
*
* @var array
*/
protected $default_options = [
// Required amount of words between ads.
'words_between_repeats' => 20,
// Whether to check the required amount of words before the first item.
'require_before_first' => false,
// Whether to check the required amount of words after the last item.
'require_after_last' => false,
'debug' => false,
'before' => false,
'paragraph_select_from_bottom' => false,
];
/**
* Amount of words between items.
*
* @var array
*/
protected $words_between = [];
/**
* Previous offset.
*
* @var false/int
*/
protected $previous_offset = false;
const START_EXISTING_AD = 'advads_amount_of_words_s';
const END_EXISTING_AD = 'advads_amount_of_words_e';
const INSERTION_POINT = 'advads_amount_of_words_i';
const SPLIT_REGEXP = '/(advads_amount_of_words_.)/';
const PREV_WORDS = 'prev_words';
const PREV_NUMBER = 'prev_number';
const NEXT_WORDS = 'next_words';
const NEXT_NUMBER = 'next_number';
const PREV_IS_AD = 'prev_is_ad';
const NEXT_IS_AD = 'next_is_ad';
/**
* Create an object of the class.
*
* @param string $html HTML string.
* @param array $options Options.
* @return object Object of this class.
*/
public static function from_html( $html, $options ) {
$libxml_previous_state = libxml_use_internal_errors( true );
$dom = new DOMDocument( '1.0', 'UTF-8' );
$success = $dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><body>' . $html );
libxml_clear_errors();
libxml_use_internal_errors( $libxml_previous_state );
$xpath = new DOMXPath( $dom );
return new static( $dom, $xpath, $options );
}
/**
* Constructor.
*
* @param DOMDocument $dom DOMDocument object.
* @param DOMXPath $xpath DOMXpath object.
* @param array $options Options.
*/
public function __construct( DOMDocument $dom, DOMXPath $xpath, array $options ) {
$this->dom = $dom;
$this->xpath = $xpath;
$this->options = array_merge( $this->default_options, $options );
}
/**
* Prepare HTML for parsing.
*
* @param array $items Existing selected items.
* @return string $r String with injected patterns.
*/
private function prepare_for_parsing( $items ) {
$expr = $this->get_expression_for_existing_ads();
$existing = $this->xpath->query( $expr );
$created = [];
// Excel existing ads.
foreach ( $existing as $node ) {
$start = $this->dom->createTextNode( self::START_EXISTING_AD );
$node->parentNode->insertBefore( $start, $node ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$created[] = $start;
$end = $this->dom->createTextNode( self::END_EXISTING_AD );
$node->parentNode->insertBefore( $end, $node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$created[] = $end;
}
// Excel items, e.g. paragraphs.
foreach ( $items as $node ) {
$point = $this->dom->createTextNode( self::INSERTION_POINT );
$created[] = $point;
if ( $this->options['before'] ) {
$node->parentNode->insertBefore( $point, $node ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
} else {
$node->parentNode->insertBefore( $point, $node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
}
}
// Select all text nodes.
$nodes = $this->xpath->query( '//text()[not(parent::script or parent::style)]' );
$r = '';
foreach ( $nodes as $n ) {
$r .= $n->data;
}
foreach ( $created as $node ) {
$node->parentNode->removeChild( $node ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
}
return $r;
}
/**
* Prepare the array that contains calculated amount of words between items.
*
* @param str $parts Post text splited by regexp.
* @return $r Word amounts between items.
*/
protected function prepare_words_between( $parts ) {
$r = [];
$prev_is_ad = false;
$doing_ad = 0;
$prev_text = [];
$prev_ip = false;
foreach ( $parts as $part ) {
if ( self::START_EXISTING_AD === $part ) {
if ( $prev_ip ) {
$r = $this->add_after_insertion_point( $r, $prev_text, true );
$prev_ip = false;
}
$prev_text = [];
$prev_is_ad = true;
++$doing_ad;
} elseif ( self::END_EXISTING_AD === $part ) {
--$doing_ad;
} elseif ( self::INSERTION_POINT === $part ) {
if ( $prev_ip ) {
$r = $this->add_after_insertion_point( $r, $prev_text );
$prev_ip = false;
}
$data = [
self::PREV_NUMBER => self::calc_words( $prev_text ),
self::PREV_IS_AD => $prev_is_ad,
];
if ( $this->options['debug'] ) {
$data[ self::PREV_WORDS ] = $prev_text;
}
$r[] = $data;
$prev_text = [];
$prev_is_ad = false;
$prev_ip = true;
} else {
$prev_text[] = self::prepare_text( $part );
}
}
if ( $prev_ip ) {
$r = $this->add_after_insertion_point( $r, $prev_text );
$prev_ip = false;
}
return $r;
}
/**
* Add words after insertion points.
*
* @param array $r Word amounts between items.
* @param array $prev_text Word amounts between items.
* @param bool $next_is_ad Whether or an ad is placed after the point.
* @return $r Word amounts between items.
*/
private function add_after_insertion_point( $r, $prev_text, $next_is_ad = false ) {
$el = array_pop( $r );
$el[ self::NEXT_NUMBER ] = self::calc_words( $prev_text );
$el[ self::NEXT_IS_AD ] = $next_is_ad;
if ( $this->options['debug'] ) {
$el[ self::NEXT_WORDS ] = $prev_text;
}
$r[] = $el;
return $r;
}
/**
* Get amount of words between items.
*
* @param string/array $items An array of `DOMElement`s or `XPath` query.
* @return array
*/
public function get_words_between( $items ) {
if ( is_string( $items ) ) {
$items = iterator_to_array( $this->xpath->query( $items ) );
}
$text = $this->prepare_for_parsing( $items );
$parts = preg_split( self::SPLIT_REGEXP, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
$this->words_between = $this->prepare_words_between( $parts );
return $this->words_between;
}
/**
* Calculate new offsets.
*
* They may be shifted to provide the minimum amount of words before and after or deleted,
* if shifting is not possible
*
* @param array $offsets Existing offsets.
* @param string/array $items An array of `DOMElement`s or `XPath` query.
* @return array $offsets New offsets.
*/
public function calc_offsets( array $offsets, $items ) {
$words_between = $this->get_words_between( $items );
$from_bottom = $this->options['paragraph_select_from_bottom'];
if ( $from_bottom ) {
rsort( $offsets );
} else {
sort( $offsets );
}
$new_offsets = [];
$this->previous_offset = false;
$this->debug( '///// debug start: ' . implode( ', ', $offsets ) . ' /////' );
foreach ( $offsets as $k => $offset ) {
if ( $from_bottom ) {
$new_offset = $this->calc_offset_from_bottom( $offset );
} else {
$new_offset = $this->calc_offset( $offset );
}
if ( false === $new_offset ) {
break;
}
$new_offsets[] = $new_offset;
}
$this->debug( '///// debug end /////' );
return $new_offsets;
}
/**
* Calculate offset.
*
* @param int $offset Offset.
*
* @return int/false New shifted offset or false.
*/
private function calc_offset( $offset ) {
$l = count( $this->words_between );
if ( false !== $this->previous_offset ) {
$offset = max( $offset, $this->previous_offset + 1 );
}
$this->debug( "///// offset: $offset /////" );
for ( $i = $offset; $i < $l; $i++ ) {
if ( ! isset( $this->words_between[ $i ] ) ) {
continue;
}
$prev_num = 0;
$next_num = 0;
$prev_has_ad = false;
for ( $j = $i; $j >= 0; $j-- ) {
if ( $this->options['debug'] ) {
$this->debug( implode( ' / ', $this->words_between[ $j ][ self::PREV_WORDS ] ) );
}
$prev_num += $this->words_between[ $j ][ self::PREV_NUMBER ];
if ( $this->words_between[ $j ][ self::PREV_IS_AD ] ) {
$prev_has_ad = true;
break;
}
}
$this->debug( $i . ': prev: ' . $prev_num );
if ( $prev_num < $this->options['words_between_repeats']
// Check if this is the first item and there are no ads before it.
&& ( $this->options['require_before_first'] || $prev_has_ad || false !== $this->previous_offset ) ) {
continue;
}
$next_has_ad = false;
for ( $k = $i; $k < $l; $k++ ) {
$next_num += $this->words_between[ $k ][ self::NEXT_NUMBER ];
if ( $this->words_between[ $k ][ self::NEXT_IS_AD ] ) {
$next_has_ad = true;
break;
}
}
if ( $next_num < $this->options['words_between_repeats']
// Check if there are no ads after the last item.
&& ( $this->options['require_after_last'] || $next_has_ad ) ) {
continue;
}
$this->previous_offset = $i;
$this->words_between[ $i ][ self::NEXT_IS_AD ] = true;
if ( isset( $this->words_between[ $i + 1 ] ) ) {
$this->words_between[ $i + 1 ][ self::PREV_IS_AD ] = true;
}
$this->debug( "found $i" );
return $i;
}
return false;
}
/**
* Calculate offset from bottom.
*
* @param int $offset Offset.
* @return int/false New shifted offset or false.
*/
private function calc_offset_from_bottom( $offset ) {
$l = count( $this->words_between );
if ( false !== $this->previous_offset ) {
$offset = min( $offset, $this->previous_offset - 1 );
}
$this->debug( "///// offset: $offset /////" );
for ( $i = $offset; $i >= 0; $i-- ) {
if ( ! isset( $this->words_between[ $i ] ) ) {
continue;
}
$prev_num = 0;
$next_num = 0;
$prev_has_ad = false;
for ( $j = $i; $j < $l; $j++ ) {
if ( $this->options['debug'] ) {
$this->debug( implode( ' / ', $this->words_between[ $j ][ self::NEXT_WORDS ] ) );
}
$prev_num += $this->words_between[ $j ][ self::NEXT_NUMBER ];
if ( $this->words_between[ $j ][ self::NEXT_IS_AD ] ) {
$prev_has_ad = true;
break;
}
}
$this->debug( $i . ': prev: ' . $prev_num );
if ( $prev_num < $this->options['words_between_repeats']
// Check if this is the first item and there are no ads before it.
&& ( $this->options['require_before_first'] || $prev_has_ad || false !== $this->previous_offset ) ) {
continue;
}
$next_has_ad = false;
for ( $k = $i; $k >= 0; $k-- ) {
$next_num += $this->words_between[ $k ][ self::NEXT_NUMBER ];
if ( $this->words_between[ $k ][ self::PREV_IS_AD ] ) {
$next_has_ad = true;
break;
}
}
if ( $next_num < $this->options['words_between_repeats']
// Check if there are no ads after the last item.
&& ( $this->options['require_after_last'] || $next_has_ad ) ) {
continue;
}
$this->previous_offset = $i;
$this->words_between[ $i ][ self::PREV_IS_AD ] = true;
if ( isset( $this->words_between[ $i - 1 ] ) ) {
$this->words_between[ $i - 1 ][ self::NEXT_IS_AD ] = true;
}
$this->debug( "found $i" );
return $i;
}
return false;
}
/**
* Get xpath expression for selecting existing ads.
*
* @return string XPath expression.
*/
protected function get_expression_for_existing_ads() {
$expr = [
// The assumption is that a `div` that has a class starting with the frontend prefix is ad.
"//div[@class and contains(concat(' ', normalize-space(@class), ' '), ' %s')]",
// Waiting for consent ads (Privacy module): `<script type="text/plain" data-tcf="waiting-for-consent" data-id="..." data-bid="..."`.
"//comment()[contains(.,'data-tcf=\"waiting-for-consent')]",
];
return sprintf(
implode( ' | ', $expr ),
sanitize_html_class( wp_advads()->get_frontend_prefix() )
);
}
/**
* Prepare text for counting words.
*
* @param str $text Text.
* @return str $text Text.
*/
protected static function prepare_text( $text ) {
$text = normalize_whitespace( $text );
$text = str_replace( "\n", ' ', $text );
// Replace punctuation.
$text = preg_replace( '/[.(),;:!?%#$¿\'"_+=\\/-]+/', '', $text );
return $text;
}
/**
* Calculate words.
*
* @param str $text Text.
* @return int Word count.
*/
protected static function calc_words( $text ) {
if ( is_array( $text ) ) {
$text = implode( ' ', $text );
}
$r = count( preg_split( '/\ +/', $text, -1, PREG_SPLIT_NO_EMPTY ) );
return $r;
}
/**
* Print info for debugging.
*
* @param str $str String.
*/
protected function debug( $str ) {
if ( $this->options['debug'] ) {
echo "\n" . esc_html( $str );
}
}
/**
* Check if 'Before Content' placement can be used.
*
* @return bool
*/
public function can_inject_before_content_placement() {
$query = '(' . $this->get_expression_for_existing_ads() . ')[1]';
$existing = $this->xpath->query( $query );
$existing = iterator_to_array( $existing );
if ( $existing ) {
$last = end( $existing );
// Select all text before the first ad.
$texts = $this->xpath->query( './/preceding::text()[not(parent::script or parent::style)]', $last );
} else {
// Select all text.
$texts = $this->xpath->query( '//text()[not(parent::script or parent::style)]' );
}
$texts = iterator_to_array( $texts );
$l = 0;
foreach ( $texts as $text ) {
$l += $this->calc_words( $this->prepare_text( $text->data ) );
}
return $l >= $this->options['words_between_repeats'];
}
/**
* Check if 'After Content' placement can be used.
*
* @return bool
*/
public function can_inject_after_content_placement() {
// Insert a node to guarantee that there is a following node after the last ad.
if ( isset( $this->dom->documentElement ) && isset( $this->dom->documentElement->lastChild ) ) {
$last_node = $this->dom->createTextNode( '/' );
$this->dom->documentElement->lastChild->appendChild( $last_node );
}
// Select following nodes of the ads.
$query = $this->get_expression_for_existing_ads() . '/following::node()[1]';
$existing = $this->xpath->query( $query );
$existing = iterator_to_array( $existing );
if ( $existing ) {
$last = end( $existing );
// Select all text before the first ad.
$texts = $this->xpath->query( './/following::text()[not(parent::script or parent::style)]', $last );
$texts = iterator_to_array( $texts );
if ( $last instanceof DOMText ) {
array_unshift( $texts, $last );
}
} else {
// Select all text.
$texts = $this->xpath->query( '//text()[not(parent::script or parent::style)]' );
$texts = iterator_to_array( $texts );
}
$l = 0;
foreach ( $texts as $text ) {
$l += $this->calc_words( $this->prepare_text( $text->data ) );
}
return $l >= $this->options['words_between_repeats'];
}
}

View File

@@ -0,0 +1,132 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
use AdvancedAds\Framework\Utilities\Params;
/**
* Compatibility fixes with other plugins.
*
* @package AdvancedAds\Pro
* @author Advanced Ads <info@wpadvancedads.com>
*/
/**
* Handles compatibility with various plugins and themes.
*/
class Advanced_Ads_Pro_Compatibility {
/**
* Constructor
*/
public function __construct() {
add_action( 'after_setup_theme', [ $this, 'after_setup_theme' ] );
// Set WPML Language.
// Note: the "Language filtering for AJAX operations" feature of WPML does not work
// because it sets cookie later then our ajax requests are sent.
if (
wp_doing_ajax() &&
defined( 'ICL_SITEPRESS_VERSION' ) &&
! empty( Params::request( 'wpml_lang' ) )
) {
do_action( 'wpml_switch_language', Params::request( 'wpml_lang' ) );
}
// Weglot plugin.
if ( function_exists( 'weglot_get_current_full_url' ) ) {
add_filter( 'advanced-ads-pro-display-condition-url-string', [ $this, 'weglot_get_current_full_url' ], 0 );
}
// Gravity forms plugin.
add_action( 'wp_loaded', [ $this, 'gravity_forms_init' ] );
}
/**
* After the theme is loaded.
*/
public function after_setup_theme() {
// Newspaper theme.
if ( defined( 'TD_THEME_NAME' ) && 'Newspaper' === TD_THEME_NAME ) {
$options = get_option( 'td_011' );
// Check if lazy load is enabled (non-existent key or '').
if ( empty( $options['tds_animation_stack'] ) ) {
add_filter( 'advanced-ads-ad-image-tag-style', [ $this, 'newspaper_theme_disable_lazy_load' ] );
}
}
}
/**
* Newspaper theme: disable lazy load of the theme to prevent conflict with cache-busting/lazy-load of the Pro add-on.
*
* @param string $style Styles.
*
* @return string
*/
public function newspaper_theme_disable_lazy_load( $style ) {
$style .= 'opacity: 1 !important;';
return $style;
}
/**
* Weglot plugin: Get the current full url that contains a lauguage.
*
* @param string $url_parameter Current URI string.
*
* @return string The modified URL parameter.
*/
public function weglot_get_current_full_url( $url_parameter ) {
if ( wp_doing_ajax() ) {
return $url_parameter;
}
$url_parsed = wp_parse_url( weglot_get_current_full_url() );
$url_parameter = $url_parsed['path'];
if ( isset( $url_parsed['query'] ) ) {
$url_parameter .= '?' . $url_parsed['query'];
}
return $url_parameter;
}
/**
* Gravity Forms plugin: Do JS initialization
*
* @return void
*/
public function gravity_forms_init() {
if ( is_admin() || ! function_exists( 'gravity_form_enqueue_scripts' ) ) {
return;
}
$has_ajaxcb_placement = false;
$gravity_form_ads = wp_advads_ad_query(
[
's' => '[gravityform id=',
'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'key' => 'allow_shortcodes',
'value' => '1',
'compare' => 'LIKE',
],
]
)->posts;
if ( empty( $gravity_form_ads ) ) {
return;
}
foreach ( wp_advads_get_placements() as $placement ) {
if ( 'on' === $placement->get_prop( 'cache-busting' ) ) {
$has_ajaxcb_placement = true;
break;
}
}
if ( ! $has_ajaxcb_placement ) {
return;
}
foreach ( $gravity_form_ads as $gravity_form_ad ) {
if ( preg_match( '#gravityform id="([0-9]+)".*#', $gravity_form_ad->post_content, $form_ids ) ) {
gravity_form_enqueue_scripts( $form_ids[1], true );
}
}
}
}

View File

@@ -0,0 +1,98 @@
<?php // phpcs:ignore WordPress.Files.FileName
use AdvancedAds\Framework\Utilities\Params;
/**
* Utils class for Advanced Ads Pro.
*/
class Advanced_Ads_Pro_Utils {
/**
* Generate unique wrapper id
*
* @return string
*/
public static function generate_wrapper_id() {
static $count = 0;
return wp_advads()->get_frontend_prefix() . ( ++$count ) . wp_rand();
}
/**
* Checks if a blog exists and is not marked as deleted.
*
* @link http://wordpress.stackexchange.com/q/138300/73
*
* @param int $blog_id Blog ID.
* @param int $site_id Site ID.
*
* @return bool
*/
public static function blog_exists( $blog_id, $site_id = 0 ) {
global $wpdb;
static $cache = [];
$site_id = absint( $site_id );
if ( 0 === $site_id ) {
$site_id = get_current_site()->id;
}
if ( empty( $cache[ $site_id ] ) ) {
// we do not test large sites.
if ( wp_is_large_network() ) {
return true;
}
$query = $wpdb->prepare( "SELECT `blog_id` FROM $wpdb->blogs WHERE site_id = %d AND deleted = 0", $site_id );
$result = $wpdb->get_col( $query ); // phpcs:ignore
// Make sure the array is always filled with something.
$cache[ $site_id ] = empty( $result ) ? [ 'checked' ] : $result;
}
return in_array( (string) $blog_id, $cache[ $site_id ], true );
}
/**
* Convert a value to non-negative integer.
*
* @param mixed $maybeint Data you wish to have converted to a non-negative integer.
* @param int $min A minimum.
* @param int $max A maximum.
*
* @return int A non-negative integer.
*/
public static function absint( $maybeint, $min = null, $max = null ) {
$int = abs( (int) $maybeint );
if ( null !== $min && $int < $min ) {
return $min;
}
if ( null !== $max && $int > $max ) {
return $max;
}
return $int;
}
/**
* Retrieve a post given a post ID
*
* Used for display conditions during `advads_ad_select` (ajax ads).
*
* @return array|WP_Post|null
*/
public static function get_post() {
$post_object = get_post();
if (
! $post_object
&& wp_doing_ajax()
&& isset( $_REQUEST['action'], $_REQUEST['theId'], $_REQUEST['isSingular'] )
&& Params::request( 'action' ) === 'advads_ad_select'
&& Params::request( 'isSingular' )
) {
$post_object = get_post( Params::request( 'theId', 0, FILTER_VALIDATE_INT ) );
}
return $post_object;
}
}