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,259 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Utilities\WordPress;
/**
* Inject content admin
*/
class Advanced_Ads_Pro_Module_Inject_Content_Admin {
public function __construct() {
// options for custom position placement
add_action( 'advanced-ads-placement-options-after', [ $this, 'custom_position_placement_options' ], 11, 2 );
// load frontend picker script
add_action( 'admin_footer', [ $this, 'frontend_picker_script' ] );
// add minimum length setting for content injection placements
add_action( 'advanced-ads-placement-options-after-advanced', [ $this, 'minimum_content_length_option' ], 10, 2 );
add_action( 'advanced-ads-placement-options-after-advanced', [ $this, 'in_any_loop_archive_pages_option' ], 10, 2 );
// Render setting that allow to prevent injection inside `the_content`
add_action( 'advanced_ads_render_post_meta_box', [ $this, 'render_post_meta_box' ], 10, 2 );
// Save setting that allow to prevent injection inside `the_content`.
add_filter( 'advanced_ads_save_post_meta_box', [ $this, 'save_post_meta_box' ] );
add_action( 'advanced-ads-placement-options-after-advanced', [ $this, 'render_option_to_skip_paragraph' ], 10, 2 );
}
/**
* render custom position placement options
*
* @since 1.1.2
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*
*/
public function custom_position_placement_options( $placement_slug, $placement ){
switch ( $placement->get_type() ) {
case 'custom_position' :
$positions = [
'insertBefore' => __( 'above', 'advanced-ads-pro' ),
'prependTo' => __( 'inside, before other content', 'advanced-ads-pro' ),
'appendTo' => __( 'inside, after other content', 'advanced-ads-pro' ),
'insertAfter' => __( 'below', 'advanced-ads-pro' )
];
$curr_position = $placement->get_prop( 'pro_custom_position' ) ?? '';
$inject_by = $placement->get_prop( 'inject_by' ) ?? 'pro_custom_element';
$container_id = $placement->get_prop( 'container_id' ) ?? '#c' . md5( $placement_slug );
ob_start(); ?>
<div id="advads-frontend-element-<?php echo $placement_slug; ?>">
<fieldset><legend>
<label><input type="radio" name="advads[placements][options][inject_by]" value="pro_custom_element" <?php
checked( $inject_by, 'pro_custom_element' ); ?>><?php _e( 'by existing element', 'advanced-ads-pro' ); ?></label>
</legend>
<p class="description"><?php _e( 'Place ads in relation to an existing element in the frontend.', 'advanced-ads-pro' ); ?></p>
<input class="advads-frontend-element advads-wide-input" type="text" name="advads[placements][options][pro_custom_element]" value="<?php
echo esc_attr( stripslashes( $placement->get_prop( 'pro_custom_element' ) ?? '' ) );
?>" placeholder="<?php _e( 'or enter manually', 'advanced-ads-pro' ); ?>"/>
<button style="display:none; color: red;" type="button" class="advads-deactivate-frontend-picker button"><?php echo esc_html_x( 'stop selection', 'frontend picker', 'advanced-ads-pro' ); ?></button>
<button type="button" class="advads-activate-frontend-picker button" data-placementid="<?php echo esc_attr( $placement_slug ); ?>"><?php esc_html_e( 'select position', 'advanced-ads-pro' ); ?></button>
<p class="description"><?php _e( 'Uses <a href="https://api.jquery.com/category/selectors/" target="_blank">jQuery selectors</a>, e.g. #container_id, .container_class', 'advanced-ads-pro' ); ?></p>
<label><?php _e( 'Position', 'advanced-ads-pro' ); ?>
<select name="advads[placements][options][pro_custom_position]">
<?php foreach( $positions as $_value => $_text ) : ?>
<option value="<?php echo $_value; ?>" <?php selected( $_value, $curr_position ); ?>><?php echo $_text; ?></option>
<?php endforeach; ?>
</select>
</label>
</fieldset>
<fieldset>
<legend><label><input type="radio" name="advads[placements][options][inject_by]" value="container_id" <?php
checked( $inject_by, 'container_id' ); ?>><?php _e( 'by new element', 'advanced-ads-pro' ); ?></label></legend>
<p class="description"><?php _e( 'Place the following element where the ad should be displayed.', 'advanced-ads-pro' ); ?></p>
<input type="text" class="advads-wide-input" name="" value="<?php echo esc_attr( sprintf( '<div id="%s"></div>', substr( $container_id, 1 ) ) ); ?>">
<input type="hidden" name="advads[placements][options][container_id]" value="<?php
echo esc_attr( $container_id ); ?>">
</fieldset>
</div><?php
$option_content = ob_get_clean();
WordPress::render_option(
'placement-custom-position',
__( 'position', 'advanced-ads-pro' ),
$option_content
);
break;
case 'archive_pages' :
$index = $placement->get_prop( 'pro_archive_pages_index' ) ?? 1;
$index = Advanced_Ads_Pro_Utils::absint( $index, 1 );
$index_option = '<input type="number" required="required" min="1" name="advads[placements][options][pro_archive_pages_index]" value="'
. $index . '" id="advads-placements-archive-pages-index' . $placement_slug . '"/>';
/* translators: %s: index of the post */
$option_content = sprintf(__( 'Inject before %s. post', 'advanced-ads-pro' ), $index_option );
$description = __( 'Before which post to inject the ad on post lists.', 'advanced-ads-pro' );
WordPress::render_option(
'placement-infeed-position',
__( 'position', 'advanced-ads-pro' ),
$option_content,
$description
);
break;
}
}
/**
* render minimum content length option for content injection placements
*
* @since 1.2.3
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*
*/
public function minimum_content_length_option( $placement_slug, $placement ){
$data = $placement->get_data();
switch ( $placement->get_type() ) {
case 'post_top' :
case 'post_bottom' :
case 'post_content' :
case 'post_content_random' :
case 'post_content_middle' :
$options = Advanced_Ads_Pro::get_instance()->get_options();
$minimum_length = ! empty( $data['pro_minimum_length'] ) ? absint( $data['pro_minimum_length'] ) : 0;
$option_content = '<input type="number" name="advads[placements][options][pro_minimum_length]" size="4" min="0" value="'. $minimum_length . '" id="advads-placement-minimum-content-length-'. $placement_slug .'"/>';
$description = __( 'Minimum length of content (in words) before automatically injected ads are allowed in them. Set to zero or leave empty to always display ads, regardless of how long the content is.', 'advanced-ads-pro' );
WordPress::render_option(
'placement-content-minimum-length',
__( 'minimum content length', 'advanced-ads-pro' ),
$option_content,
$description
);
break;
}
}
/**
* Render inject in any loop option for post list placement.
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function in_any_loop_archive_pages_option( $placement_slug, $placement ) {
$data = $placement->get_data();
switch ( $placement->get_type() ) {
case 'archive_pages' :
$options = advanced_ads_pro::get_instance()->get_options();
$in_any_loop = ! empty( $data['in_any_loop'] );
$option_content = '<input type="checkbox" name="advads[placements][options][in_any_loop]" value="1" ' . checked( $in_any_loop, 1, false ) . ' />';
$description = __( 'Allow injection into any custom and secondary queries.', 'advanced-ads-pro' );
$description .= ' ' . __( 'Only enable this option if you are sure what you are doing!', 'advanced-ads-pro' );
WordPress::render_option(
'placement-infeed-any-loop',
__( 'secondary loops', 'advanced-ads-pro' ),
$option_content,
$description
);
break;
}
}
/**
* load frontend picker javascript
*
* @since 1.1.2
*/
public function frontend_picker_script(){
$screen = get_current_screen();
// Check if the following code is included in the basic plugin.
if ( 0 <= version_compare( ADVADS_VERSION, '1.19' ) || 'edit-advanced_ads_plcmnt' !== $screen->id ) {
return;
}
?><script>jQuery( document ).ready( function(){
// set element from frontend into placement input field
if( localStorage.getItem( 'advads_frontend_element' )){
var placement = localStorage.getItem( 'advads_frontend_picker' );
var id = 'advads-frontend-element-' + placement;
jQuery( '[id="' + id + '"]' ).find( '.advads-frontend-element' ).val( localStorage.getItem( 'advads_frontend_element' ) );
var action = localStorage.getItem( 'advads_frontend_action' );
if (typeof(action) !== 'undefined'){
var show_all_link = jQuery( 'a[data-placement="' + placement + '"]');
Advanced_Ads_Admin.toggle_placements_visibility( show_all_link );
}
localStorage.removeItem( 'advads_frontend_action' );
localStorage.removeItem( 'advads_frontend_element' );
localStorage.removeItem( 'advads_frontend_picker' );
localStorage.removeItem( 'advads_prev_url' );
}
jQuery('.advads-activate-frontend-picker').on( 'click', function( e ){
localStorage.setItem( 'advads_frontend_picker', jQuery( this ).data('placementid') );
localStorage.setItem( 'advads_frontend_action', jQuery( this ).data('action') );
localStorage.setItem( 'advads_prev_url', window.location );
window.location = "<?php echo home_url(); ?>";
});
// allow to deactivate frontend picker
if ( localStorage.getItem( 'advads_frontend_picker' ) ) {
var id = 'advads-frontend-element-' + localStorage.getItem( 'advads_frontend_picker' );
jQuery( '[id="' + id + '"]' ).find( '.advads-deactivate-frontend-picker' ).show();
}
jQuery( '.advads-deactivate-frontend-picker' ).on( 'click', function( e ) {
localStorage.removeItem( 'advads_frontend_action' );
localStorage.removeItem( 'advads_frontend_element' );
localStorage.removeItem( 'advads_frontend_picker' );
localStorage.removeItem( 'advads_prev_url' );
jQuery('.advads-deactivate-frontend-picker').hide();
});
});
</script><?php
}
/**
* Render setting that allow to prevent injection inside `the_content`.
*
* @param WP_Post $post The post object.
* @param mixed $values existing values from database
*/
public function render_post_meta_box( $post, $values ) {
require plugin_dir_path(__FILE__) . '/views/setting_post_meta_box.php';
}
/**
* Sanitize and save setting that allow to prevent injection inside `the_content`.
*
* @param array $_data data sent by user
* @return $_data sanitized data
*/
public function save_post_meta_box( $_data = [] ) {
$_data['disable_the_content'] = isset( $_POST['advanced_ads']['disable_the_content'] ) ? absint( $_POST['advanced_ads']['disable_the_content'] ) : 0;
return $_data;
}
/**
* Render the option to skip paragraph.
*
* @param string $placement_slug Placement id.
* @param Placement $placement Placement instance.
*/
public function render_option_to_skip_paragraph( $placement_slug, $placement ) {
if ( ! $placement->is_type( [ 'post_content_middle', 'post_top', 'post_bottom', 'post_content' ] ) ) {
return;
}
$data = $placement->get_data();
$words_between_repeats = ! empty( $data['words_between_repeats'] ) ? absint( $data['words_between_repeats'] ) : 0;
ob_start();
require AAP_BASE_PATH . '/views/setting_words_between_ads.php';
$setting = ob_Get_clean();
WordPress::render_option(
'advanced-ads-pro-skip-paragraph',
__( 'Words between ads', 'advanced-ads-pro' ),
$setting
);
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,172 @@
<?php //phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase, WordPress.Files.FileName.InvalidClassFileName
use Symfony\Component\CssSelector\CssSelectorConverter;
/**
* Injects ads using an output buffer.
*
* Since we call `ob_start` during ad injection, we do not specify the callback in order to prevent the
* "ob_start(): Cannot use output buffering in output buffering display handlers" error.
*/
class Advanced_Ads_Pro_Module_Inject_Content_Custom_Position {
/**
* Constructor.
*/
public function __construct() {
if ( 'php' !== Advanced_Ads_Pro::get_instance()->get_options()['placement-positioning'] ) {
return;
}
add_action( 'wp_head', [ $this, 'start_output_buffering' ], 20 );
// We need to inject ads (collect Cache Busting output) earlier than Cache Busting outputs its scripts at priority `21`.
add_action( 'wp_footer', [ $this, 'stop_output_buffering' ], 20 );
}
/**
* Start output buffering.
*/
public function start_output_buffering() {
ob_start();
}
/**
* Stop output buffering.
*/
public function stop_output_buffering() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- page content should be unescaped.
echo $this->maybe_inject_placements( ob_get_clean() );
}
/**
* Maybe inject some custom position placements.
*
* @param string $content Buffered page content.
* @return string
*/
public function maybe_inject_placements( $content ) {
if ( ! class_exists( 'Advanced_Ads_In_Content_Injector' ) ) {
return $content;
}
foreach ( wp_advads_get_placements() as $placement_id => $placement ) {
if (
empty( $placement->get_item() ) || ! $placement->is_type( [ 'custom_position', 'post_above_headline' ] )
) {
continue;
}
$placement_options = $placement->get_data();
if ( $placement->is_type( 'custom_position' ) ) {
$xpath_options = $this->get_custom_position_xpath_options( $placement_options );
if ( ! $xpath_options ) {
continue;
}
}
if ( $placement->is_type( 'post_above_headline' ) ) {
$xpath_options = [
'tag' => 'custom',
'xpath' => '//h1',
'position' => 'before',
];
}
$content = Advanced_Ads_In_Content_Injector::inject_in_content(
$placement_id,
array_merge( $placement_options, $xpath_options ),
$content,
[
'allowEmpty' => true,
'alter_nodes' => false,
'itemLimit' => -1,
'repeat' => true,
'paragraph_id' => 1,
]
);
}
return $content;
}
/**
* Get XPath options for Custom Position placement.
*
* @param array $placement_options Placement options.
* @return array|bool XPath options or False on failure.
*/
private function get_custom_position_xpath_options( $placement_options ) {
if ( ( ! isset( $placement_options['inject_by'] ) || 'pro_custom_element' === $placement_options['inject_by'] ) && isset( $placement_options['pro_custom_element'] ) ) {
// By CSS selector.
$xpath = $this->css_to_xpath( $placement_options['pro_custom_element'] );
if ( ! $xpath ) {
return false;
}
$positions = [
'insertBefore' => 'before',
'prependTo' => 'prepend',
'appendTo' => 'append',
'insertAfter' => 'after',
];
return [
'tag' => 'custom',
'xpath' => $xpath,
'position' => isset( $placement_options['pro_custom_position'], $positions[ $placement_options['pro_custom_position'] ] )
? $positions[ $placement_options['pro_custom_position'] ]
: 'before',
];
} elseif ( isset( $placement_options['container_id'] ) ) {
// By HTML container.
$xpath = $this->css_to_xpath( $placement_options['container_id'] );
if ( ! $xpath ) {
return false;
}
return [
'tag' => 'custom',
'xpath' => $xpath,
'position' => 'append',
];
}
return false;
}
/**
* Translate a CSS expression into corresponding XPath expression.
*
* @param string $css CSS Expression.
* @return string XPath Expression.
*/
private function css_to_xpath( $css ) {
if ( ! $css ) {
return '';
}
// Our "frontend picker" adds the `:eq` selector.
// Since the "css-selector" library does not support it, we replace it with an unique tag.
$css = preg_replace( '/:eq\((\d+)\)/', ' > advads_eq_selector$1', $css );
try {
$query = ( new CssSelectorConverter() )->toXPath( $css );
} catch ( Exception $e ) {
return '';
}
// Remove the unique tag and implement the functionality of the `:eq` selector.
do {
$query = preg_replace_callback(
'/(.*?)\/advads_eq_selector(\d+)/',
static function ( $matches ) {
return sprintf( '(%s)[%s]', $matches[1], $matches[2] + 1 );
},
$query,
1,
$count
);
} while ( 1 === $count );
return $query;
}
}

View File

@@ -0,0 +1,802 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Constants;
use AdvancedAds\Abstracts\Ad;
use AdvancedAds\Abstracts\Group;
use AdvancedAds\Abstracts\Placement;
use AdvancedAds\Utilities\Conditional;
/**
* Inject Content module
*/
class Advanced_Ads_Pro_Module_Inject_Content {
/**
* Constructor.
*/
public function __construct() {
// TODO: load options.
add_filter( 'the_content', [ $this, 'inject_content' ], 100 );
// action after ad output is created; used for js injection.
add_filter( 'advanced-ads-ad-output', [ $this, 'after_ad_output' ], 10, 2 );
// action after group output is created; used for js injection.
add_filter( 'advanced-ads-group-output', [ $this, 'after_group_output' ], 10, 2 );
// check if content injection is limited for longer texts only.
add_filter( 'advanced-ads-can-inject-into-content', [ $this, 'check_content_length' ], 10, 3 );
// Allow to prevent injection inside `the_content`.
add_action( 'advanced-ads-can-inject-into-content', [ $this, 'prevent_injection_the_content' ], 10, 3 );
add_action( 'wp_footer', [ $this, 'inject_footer' ], 20 );
add_action( 'the_post', [ $this, 'inject_loop_post' ], 20, 2 );
// Add ads into AMP archive pages created by the AMP for WP plugin.
add_action( 'ampforwp_between_loop', [ $this, 'inject_loop_post_amp_for_wp' ] );
// Support custom hook for content injections.
if ( defined( 'ADVANCED_ADS_PRO_CUSTOM_CONTENT_FILTER' ) ) {
add_filter( ADVANCED_ADS_PRO_CUSTOM_CONTENT_FILTER, [ Advanced_Ads::get_instance(), 'inject_content' ] );
add_filter( ADVANCED_ADS_PRO_CUSTOM_CONTENT_FILTER, [ $this, 'inject_content' ] );
}
add_filter( 'advanced-ads-cache-busting-item', [ $this, 'inject_js_before_cache_busting_output' ], 10, 2 );
add_action( 'init', [ $this, 'add_skip_paragraph_filters' ], 30 );
// Check if ads can be displayed by post type.
add_filter( 'advanced-ads-can-display-ad', [ $this, 'can_display_by_post_type' ], 10, 2 );
// Check if Verification code & Auto ads ads can be displayed by post type.
add_filter( 'advanced-ads-can-display-ads-in-header', [ $this, 'can_display_in_header_by_post_type' ], 10 );
add_action( 'advanced-ads-body-classes', [ $this, 'body_class' ] );
add_filter( 'advanced-ads-set-wrapper', [ $this, 'add_ad_wrapper_id' ], 10, 2 );
add_filter( 'advanced-ads-ad-health-nodes', [ $this, 'add_ad_health_nodes' ] );
}
/**
* Injected ad randomly into post content.
*
* @since 1.0.0
* @param string $content Post content.
*/
public function inject_content( $content = '' ) {
$options = Advanced_Ads::get_instance()->options();
// do not inject in content when on a BuddyPress profile upload page (avatar & cover image).
if ( ( function_exists( 'bp_is_user_change_avatar' ) && bp_is_user_change_avatar() ) || ( function_exists( 'bp_is_user_change_cover_image' ) && bp_is_user_change_cover_image() ) ) {
return $content;
}
if ( $this->has_many_the_content() ) {
return $content;
}
// Check if ads are disabled in secondary queries.
if ( ! empty( $options['disabled-ads']['secondary'] ) ) {
if ( wp_doing_ajax() ) {
// This function was called by ajax (in secondary query).
return $content;
}
// get out of wp_router_page post type if ads are disabled in secondary queries.
if ( 'wp_router_page' === get_post_type() ) {
return $content;
}
}
// No need to inject ads because all tags are stripped from excepts.
if ( doing_filter( 'get_the_excerpt' ) ) {
return $content;
}
// run only within the loop on single pages of public post types.
$public_post_types = get_post_types( [ 'public' => true, 'publicly_queryable' => true ], 'names', 'or' );
// make sure that no ad is injected into another ad
if ( get_post_type() == Constants::POST_TYPE_AD ) {
return $content;
}
// Do not inject on admin pages.
if ( is_admin() && ! wp_doing_ajax() ) {
return $content;
}
$is_amp = function_exists( 'advads_is_amp' ) && advads_is_amp();
// check if admin allows injection in all places.
if ( ! isset( $options['content-injection-everywhere'] ) ) {
// check if this is a singular page within the loop or an amp page.
if ( ( ! is_singular( $public_post_types ) && ! is_feed() ) || ( ! $is_amp && ! in_the_loop() ) ) {
return $content;
}
}
$placements = wp_advads_get_placements();
if ( ! apply_filters( 'advanced-ads-can-inject-into-content', true, $content, $placements ) ) {
return $content;
}
if ( is_array( $placements ) ) {
foreach ( $placements as $placement ) {
if ( empty( $placement->get_item() ) ) {
continue;
}
if ( $placement->is_type( ['post_content_random', 'post_above_headline', 'post_content_middle'] ) ) {
// Dont inject above headline on non-singular pages.
if ( $placement->is_type( 'post_above_headline' ) && ( ! is_singular( $public_post_types ) ||( $is_amp && ! $this->use_output_buffering() ) ) ) {
continue;
}
// Check if injection is ok for a specific placement id.
if ( ! apply_filters( 'advanced-ads-can-inject-into-content-' . $placement->get_id(), true, $content, $placement ) ) {
continue;
}
$placement_data = $placement->get_data();
$placement_data['placement']['type'] = $placement->get_type();
switch ( $placement->get_type() ) {
case 'post_above_headline':
if ( ! $this->use_output_buffering() ) {
$placement_data['lazy_load'] = 'disabled';
$content .= get_the_placement( $placement->get_id(), '', $placement_data );
}
break;
case 'post_content_middle' :
$content = Advanced_Ads_In_Content_Injector::inject_in_content( $placement->get_id(), $placement_data, $content );
break;
case 'post_content_random' :
if ( $this->content_random_use_js( $placement ) ) {
$content .= get_the_placement( $placement->get_id(), '', $placement_data );
} else {
$content = Advanced_Ads_In_Content_Injector::inject_in_content( $placement->get_id(), $placement_data, $content );
}
break;
}
}
}
}
return $content;
}
/**
* Inject custom position placements
*
* @since 1.1.2
*/
public function inject_footer() {
if ( $this->use_output_buffering() ) {
return;
}
$placements = wp_advads_get_placements();
foreach ( $placements as $placement ) {
if ( ! $placement->is_type( 'custom_position' ) || ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) ) {
continue;
}
$placement_options = $placement->get_data();
if ( isset( $placement_options['lazy_load'] ) ) {
$placement_options['lazy_load'] = 'disabled';
}
echo get_the_placement( $placement->get_id(), '', $placement_options );
}
}
/**
* Add ad wrapper ID if there is none. Needed for random paragraph placement.
*
* @param array $wrapper the ad wrapper array.
* @param Ad $ad the ad.
*
* @return array
*/
public function add_ad_wrapper_id( $wrapper, $ad ) {
$placement = $ad->get_root_placement();
// Don't mess with anything but top level ad in an xpath based content placement
if ( ! $ad->is_top_level() || ! $placement || ! $placement->is_type( [ 'post_content_random', 'post_above_headline', 'custom_position' ] ) ) {
return $wrapper;
}
if ( empty( $wrapper['id'] ) ) {
$wrapper['id'] = $ad->create_wrapper_id();
}
return $wrapper;
}
/**
* Inject ad output and js code.
*
* @param string $content The ad content.
* @param Ad $ad The ad object.
*
* @return string
*/
public function after_ad_output( $content, Ad $ad ) {
$previous_method = $ad->get_prop( 'previous_method' );
if ( null !== $previous_method && Constants::ENTITY_GROUP === $previous_method ) {
return $content;
}
$wrapper = $ad->create_wrapper();
if ( empty( $wrapper['id'] ) ) {
$wrapper['id'] = $ad->create_wrapper_id();
}
if ( null === $ad->get_prop( 'cache_busting_elementid' ) ) {
$content .= $this->get_output_js( $wrapper['id'], $ad );
}
return $content;
}
/**
* inject js code after group output
*
* @param string $output_string Final group output.
* @param Group $group Group instance.
*/
public function after_group_output( $output_string, Group $group ) {
if ( $output_string ) {
if ( ! $group->get_prop( 'cache_busting_elementid' ) ) {
$wrapper_id = Advanced_Ads_Pro_Utils::generate_wrapper_id();
if ( $js_output = $this->get_output_js( $wrapper_id, $group ) ) {
$output_string = '<div id="' . $wrapper_id . '">' . $output_string . '</div>' . $js_output;
}
}
}
return $output_string;
}
/**
* Get js to append after ad/group output.
*
* @param string $wrapper_id wrapper ID.
* @param Ad|Group $entity the entity.
*
* @return string
*/
private function get_output_js( $wrapper_id, $entity ) {
$content = '';
// Do not inject js on AMP pages.
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) { return $content; }
$ad_args = $entity->get_prop( 'ad_args' );
// Group refresh: do not move if the top level wrapper was moved earlier.
if ( is_a_group( $entity ) && $entity->get_prop( 'group_refresh' ) && ! $entity->get_prop( 'group_refresh.is_top_level' ) ) {
return $content;
}
$parent = $entity->get_parent();
if ( ! $parent ) {
return $content;
}
// Move only the most outer group wrapper.
if ( $parent && ! is_a_placement( $parent ) ) {
return $content;
}
switch( $parent->get_type() ) {
case 'post_content_random' :
if ( ! $this->content_random_use_js( $parent ) ) {
return '';
}
$paragraphs_selector = $this->get_paragraph_selector( $ad_args );
$content .= 'var advads_content_p = jQuery("#'. $wrapper_id .'")' . $paragraphs_selector . ';'
. 'var advads_content_random_p = advads_content_p.eq( Math.round(Math.random() * ( advads_content_p.length - 1) ) );'
. 'if ( advads_content_random_p.length ) { advads.move("#'. $wrapper_id .'", advads_content_random_p, { method: "insertAfter" }); }';
break;
case 'post_above_headline':
if ( $this->use_output_buffering() ) {
return '';
}
$content .= 'advads.move("#'. $wrapper_id .'", "h1", { method: "insertBefore" });';
break;
case 'custom_position':
if ( $this->use_output_buffering() ) {
return '';
}
// By element Selector.
$inject_by = $parent->get_prop( 'inject_by' );
if ( ! $inject_by || 'pro_custom_element' === $inject_by ) {
$target = $parent->get_prop( 'pro_custom_element' ) ?? '';
$position = $parent->get_prop( 'pro_custom_position' ) ?? 'insertBefore';
// By HTML container.
} else {
$target = $parent->get_prop( 'container_id' ) ?? '';
$position = 'appendTo';
}
$options[] = 'method: "' . $position . '"';
// check if can be moved into hidden elements
if ( defined( 'ADVANCED_ADS_PRO_CUSTOM_POSITION_MOVE_INTO_HIDDEN' ) ) {
$options[] = 'moveintohidden: "true"';
}
$content .= 'advads.move("#' . $wrapper_id . '", "' . $target . '", { ' . implode( ', ', $options ) . ' });';
break;
}
if ( $content ) {
if ( ! empty( $parent->get_prop( 'cache_busting_elementid' ) ) ) {
// Document is ready. Do not use another 'ready' block so that the wrapper is moved before executing js in ad content.
$content = '<script>' . $content . '</script>';
} else {
$content = '<script>( window.advanced_ads_ready || jQuery( document ).ready ).call( null, function() {' . $content . '});</script>';
}
}
return $content;
}
/**
* get paragraph selector for js depending on cache busting settings
*
* @since 1.2.3
*
* @return string $paragraph_selector
*/
private function get_paragraph_selector( $args ) {
$plugin_options = Advanced_Ads::get_instance()->options();
$content_injection_level_disabled = isset( $plugin_options['content-injection-level-disabled'] );
/**
* find paragraphs
* which are not within tables
* which are not within blockquotes
* which are not empty
* which are not within an image caption
*
* depending on "Disable injection limitation" setting,
* either inject into all p tags, including subordinated
* or only direct and preceding siblings
*/
if ( $content_injection_level_disabled ) {
$paragraphs_selector = '.parent().find("p:not(table p):not(blockquote p):not(div.wp-caption p)").filter(function() {return this.innerHTML.trim()!==""})';
} else {
// Do not use 'prevAll' because it returns elements in reverse order.
$paragraphs_selector = '.parent().children("p:not(table p):not(blockquote p):not(div.wp-caption p)").filter(function() {return this.innerHTML.trim()!==""})';
}
return apply_filters( 'advanced-ads-pro-inject-content-selector', $paragraphs_selector );
}
/**
* Check content length for injecting ads into the post content
*
* @param bool $inject whether to inject or not
* @param string $content post content
* @param array $placements array with all placements
*
* @return bool true, if injection is ok
*/
public function check_content_length( $inject = true, $content = '', $placements = [] ) {
if ( ! $inject ) {
return false;
}
if ( defined( 'ADVADS_CURRENT_CONTENT_LENGTH' ) ) {
return $inject;
}
// content injection placements
$cj_placements = [ 'post_top', 'post_bottom', 'post_content', 'post_content_random', 'post_content_middle' ];
// find out of content injection placements are defined at all
$has_content_placements = false;
foreach( $placements as $placement_id => $placement ) {
if ( false !== $placement && $placement->is_type( $cj_placements ) ) {
$has_content_placements = true;
// register filter for placement specific length check
add_filter( 'advanced-ads-can-inject-into-content-' . $placement_id, [ $this, 'check_placement_minimum_length' ], 10, 3 );
}
}
if ( $has_content_placements ) {
// Remove all HTML tags and comments and count spaces in content.
$length = (int) preg_match_all( '/\s+/', wp_strip_all_tags( $content ) );
define( 'ADVADS_CURRENT_CONTENT_LENGTH', $length );
}
return $inject;
}
/**
* Allow to prevent injections inside `the_content`.
*
* @param bool $inject Whether to inject or not
* @param string $content Post content
* @param array $placements Array with all placements
*
* @return bool true, if injection is ok
*/
public function prevent_injection_the_content( $inject = true, $content = '', $placements = [] ) {
if ( ! $inject ) {
return false;
}
global $post;
if ( empty( $post->ID ) ) {
return true;
}
$post_ad_options = get_post_meta( $post->ID, '_advads_ad_settings', true );
return empty( $post_ad_options['disable_the_content'] );
}
/**
* Check if placement should be displayed by content length setting of content injection placements.
*
* @param bool $return Whether to inject or not.
* @param string $content Post content.
* @param Placement $placement Placement instance.
*
* @return bool
*/
public function check_placement_minimum_length( $return, $content, $placement ) {
if ( ! $return ) {
return false;
}
$minimum_length = $placement->get_prop( 'pro_minimum_length' );
if ( ! $minimum_length ) {
return $return;
}
if (
defined('ADVADS_CURRENT_CONTENT_LENGTH') &&
ADVADS_CURRENT_CONTENT_LENGTH < absint( $minimum_length ) ) {
return false;
}
return $return;
}
/**
* echo ad before/after posts in loops on archive pages
*
* @since 1.2.1
* @param array $post post object
* @param WP_Query $wp_query query object
*/
public function inject_loop_post( $post, $wp_query = null ) {
$is_ajax = wp_doing_ajax();
if ( ! $wp_query instanceof WP_Query || is_feed() || ( is_admin() && ! $is_ajax ) ) {
return;
}
$plugin_options = Advanced_Ads::get_instance()->options();
// only inject on AJAX requests when Secondary Query option is enabled
if ( ! empty( $plugin_options['disabled-ads']['secondary'] ) && $is_ajax ) {
return;
}
if ( ! isset( $wp_query->current_post )) {
return;
};
// dont inject into main query on single pages.
if ( $wp_query->is_main_query() && is_single() ) {
return;
}
$curr_index = $wp_query->current_post + 1; // normalize index
// 'wp_reset_postdata()' does 'the_post' action.
// handle the situation when wp_reset_postdata() is used after secondary query inside main query.
static $handled_indexes = [];
if ( $wp_query->is_main_query() ) {
if ( in_array( $curr_index, $handled_indexes ) ) {
return;
}
$handled_indexes[] = $curr_index;
}
$placements = wp_advads_get_placements();
if ( ! empty( $placements ) ) {
foreach ( $placements as $_placement_id => $placement ) {
if ( empty( $placement->get_item() ) ) {
continue;
}
if ( $placement->is_type( 'archive_pages' ) ) {
$options = $placement->get_data();
if ( empty( $options['in_any_loop'] )
&& ( $wp_query->is_singular() || ! $wp_query->in_the_loop || ! $wp_query->is_main_query() ) ) {
continue;
}
// Check if the loop is outside wp_head, but only on non-AJAX calls.
if ( ! is_admin() && ! did_action( 'wp_head' ) ) {
continue;
}
$injection_index = ! empty ( $options['pro_archive_pages_index'] ) ? absint( $options['pro_archive_pages_index'] ) : 1;
if ( $injection_index === $curr_index ) {
// todo: leave a comment about the use of the next line. Might be needed to submit placement information to options.
$options['placement']['type'] = $placement->get_type();
echo get_the_placement( (int) $_placement_id, '', $options );
}
}
}
}
}
/**
* Insert an ad in the loop for archive pages created by AMP for WP (https://wordpress.org/plugins/accelerated-mobile-pages/)
*
* We can ommit the checks in inject_loop_post() here because the ampforwp_between_loop hook should provide the right position.
*
* @param int $count index of the current position in the loop.
*/
public function inject_loop_post_amp_for_wp( $count ) {
$placements = wp_advads_get_placements_by_types( 'archive_pages' );
foreach ( $placements as $id => $placement ) {
if ( ! $placement->get_item() ) {
continue;
}
$options = $placement->get_data();
if ( isset( $options['pro_archive_pages_index'] ) ) {
$ad_index = absint( $options['pro_archive_pages_index'] );
// We need to reduce our index by one to match how AMP for WP counts the index.
if ( ( $ad_index - 1 ) === $count ) {
// Todo: leave a comment about the use of the next line. Might be needed to submit placement information to options.
$options['placement']['type'] = $placement->get_type();
echo get_the_placement( $id, '', $options ); // phpcs:ignore
}
}
}
}
/**
* Find the calls to `the_content` inside functions hooked to `the_content`.
*
* @return bool
*/
public function has_many_the_content() {
global $wp_current_filter;
if ( count( array_keys( $wp_current_filter, 'the_content', true ) ) > 1 ) {
// More then one `the_content` in the stack.
return true;
}
return false;
}
/**
* Check whether to use JS to position the placement.
*
* @param Placement $placement Placement options.
*
* @return bool
*/
private function content_random_use_js( $placement ) {
if ( function_exists( 'advads_is_amp' ) && advads_is_amp() ) {
return false;
}
if ( ! Advanced_Ads_Pro_Module_Cache_Busting::is_enabled() ) {
return false;
}
$cb = $placement->get_prop( 'cache-busting' );
// Check if we did not switch to `off` from `auto` (off as fallback method).
if ( 'off' === $cb && ! $placement->get_prop( 'cache-busting-orig' ) ) {
return false;
}
return true;
}
/**
* Check whether or not to use PHP to position placements.
*
* @return bool
*/
private function use_output_buffering() {
return Advanced_Ads_Pro::get_instance()->get_options()['placement-positioning'] === 'php';
}
/**
* Inject a script to output before cache busting output.
* The script moves the empty wrapper because some ad networks do not allow to move ads inserted to the DOM.
*
* @param array $response Cache busting item.
* @param array $request Request info.
*
* @return array $r Cache busting item.
*/
function inject_js_before_cache_busting_output( $response, $request ) {
if ( ! isset( $request['method'] ) || 'placement' !== $request['method']
|| empty( $request['args']['cache_busting_elementid'] ) ) {
return $response;
}
$placement = wp_advads_get_placement( (int) ( $request['args']['previous_id'] ?? $request['id'] ) );
$placement->set_prop_temp( 'ad_args', $request['args'] );
$el_id = $request['args']['cache_busting_elementid'];
$response['inject_before'][] = $this->get_output_js( $el_id, $placement->get_item_object() );
return $response;
}
/**
* Add filters to skip paragraph.
*/
public function add_skip_paragraph_filters() {
foreach ( wp_advads_get_placements() as $placement_id => $placement ) {
if ( ! empty( $placement->get_prop( 'words_between_repeats' ) ) ) {
add_filter( 'advanced-ads-can-inject-into-content-' . $placement_id, [ $this, 'maybe_skip_content_placement' ], 10, 3 );
}
}
}
/**
* Check if the "Before/After Content" placement has enough words before.
*
* @param bool $return Whether to inject or not.
* @param string $content Post content.
* @param Placement $placement Placement instance.
*
* @return bool
*/
public function maybe_skip_content_placement( $return, $content, $placement ) {
if ( ! $return ) {
return false;
}
if ( $placement->is_type( [ 'post_top', 'post_bottom' ] ) ) {
return $return;
}
$words = $placement->get_prop( 'words_between_repeats' );
if ( ! empty( $words ) ) {
$options['words_between_repeats'] = absint( $words );
$offset_shifter = Advanced_Ads_Pro_Offset_Shifter::from_html( $content, $options );
return $placement->is_type( 'post_top' )
? $offset_shifter->can_inject_before_content_placement()
: $offset_shifter->can_inject_after_content_placement();
}
return $return;
}
/**
* Check if the ad should be displayed based on post type.
*
* @param bool $can_display True if the ad should be displayed, false otherwise.
* @param Ad $ad Ad object.
* @return bool True if the ad should be displayed, false otherwise.
*/
public function can_display_by_post_type( $can_display, Ad $ad ) {
if ( ! $can_display ) {
return false;
}
$post_type = $ad->get_prop( 'ad_args.post.post_type' );
if ( ! empty( $post_type )
&& $this->post_type_disabled( $post_type ) ) {
return false;
}
return true;
}
/**
* Return true if the ad can be displayed in the header of the page depending on the current post type.
*
* @param bool $can_display if the ad can already be displayed.
*
* @return bool
*/
public function can_display_in_header_by_post_type( $can_display ) {
if ( ! $can_display ) {
return false;
}
$post_type = $this->get_current_post_type();
if ( $this->post_type_disabled( $post_type ) ) {
return false;
}
return true;
}
/**
* Get current post type.
*
* @return bool|string False on failure or post type on success.
*/
private function get_current_post_type() {
global $wp_the_query, $post;
// If currently on a single site, use the main query information just in case a custom query is broken.
if ( isset( $wp_the_query->post->post_type ) && $wp_the_query->is_single() ) {
return $wp_the_query->post->post_type;
} elseif ( isset( $post->post_type ) ) {
return $post->post_type;
}
return false;
}
/**
* Check if post type disabled.
*
* @param string $post_type Post type.
* @return bool
*/
private function post_type_disabled( $post_type ) {
$options = Advanced_Ads_Pro::get_instance()->get_options();
if ( ! empty( $options['general']['disable-by-post-types'] )
&& is_array( $options['general']['disable-by-post-types'] )
&& in_array( $post_type, $options['general']['disable-by-post-types'], true ) ) {
return true;
}
return false;
}
/**
* Add classes to the `body` tag.
*
* @param string[] $aa_classes Array of existing class names.
* @return string[] $aa_classes Array of existing and new class names.
*/
public function body_class( $aa_classes ) {
$post_type = $this->get_current_post_type();
if ( $this->post_type_disabled( $post_type ) ) {
$aa_classes[] = 'aa-disabled-post-type';
}
return $aa_classes;
}
/**
* List health notifications on the page in the admin-bar.
*
* @param arr $nodes Admin bar nodes.
*
* @return arr $nodes Admin bar nodes.
*/
public function add_ad_health_nodes( $nodes ) {
if ( $this->post_type_disabled( $this->get_current_post_type() ) ) {
$nodes[] = [
'type' => 2,
'data' => [
'parent' => 'advanced_ads_ad_health',
'id' => 'advanced_ads_ad_health_no_post',
'title' => __( 'Ads are disabled for this Post Type', 'advanced-ads' ),
'href' => admin_url( 'admin.php?page=advanced-ads-settings' ),
'meta' => [
'class' => 'advanced_ads_ad_health_warning',
'target' => '_blank',
],
],
];
}
return $nodes;
}
}

View File

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

View File

@@ -0,0 +1,4 @@
<br />
<label><input type="checkbox" name="advanced_ads[disable_the_content]" value="1" <?php
if ( isset( $values['disable_the_content'] ) ) { checked( $values['disable_the_content'], true ); }
?>/><?php _e( 'Disable automatic ad injection into the content', 'advanced-ads-pro' ); ?></label>