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,487 @@
<?php // phpcs:ignoreFile
use AdvancedAds\Framework\Utilities\Params;
use AdvancedAds\Utilities\Conditional;
/**
* User interface for managing the 'ads.txt' file.
*/
class Advanced_Ads_Ads_Txt_Admin {
/**
* Ads.txt data management class
*
* @var Advanced_Ads_Ads_Txt_Strategy
*/
private $strategy;
/**
* Ads.txt frontend logic class
*
* @var Advanced_Ads_Ads_Txt_Public
*/
private $public;
/**
* AdSense network ID.
*/
const adsense = 'adsense';
const ACTION = 'wp_ajax_advads-ads-txt';
/**
* Whether the notices should be updated via AJAX because no cached data exists.
*
* @var bool
*/
private $notices_are_stale = false;
/**
* Constructor
*
* @param Advanced_Ads_Ads_Txt_Strategy $strategy Ads.txt data management class.
* @param Advanced_Ads_Ads_Txt_Public $public Ads.txt frontend logic class.
*/
public function __construct( Advanced_Ads_Ads_Txt_Strategy $strategy, Advanced_Ads_Ads_Txt_Public $public ) {
$this->strategy = $strategy;
$this->public = $public;
add_filter( 'advanced-ads-sanitize-settings', [ $this, 'toggle' ], 10, 1 );
add_action( 'pre_update_option_advanced-ads-adsense', [ $this, 'update_adsense_option' ], 10, 2 );
add_action( 'advanced-ads-settings-init', [ $this, 'add_settings' ] );
add_action( self::ACTION, [ $this, 'ajax_refresh_notices' ] );
}
/**
* Toggle ads.txt and add additional content.
*
* @param array $options Options.
* @return array $options Options.
*/
public function toggle( $options ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing
$create = ! empty( Params::post( 'advads-ads-txt-create' ) );
$all_network = ! empty( Params::post( 'advads-ads-txt-all-network' ) );
$additional_content = trim( wp_unslash( Params::post( 'advads-ads-txt-additional-content', '' ) ) );
// phpcs:enable
$this->strategy->toggle( $create, $all_network, $additional_content );
$content = $this->get_adsense_blog_data();
$this->strategy->add_network_data( self::adsense, $content );
$r = $this->strategy->save_options();
if ( is_wp_error( $r ) ) {
add_settings_error(
'advanced-ads-adsense',
'adsense-ads-txt-created',
$r->get_error_message(),
'error'
);
}
return $options;
}
/**
* Update the 'ads.txt' file every time the AdSense settings are saved.
* The reason for not using `update_option_*` filter is that the function
* should also get called for newly added AdSense options.
*
* @param array $prev Previous options.
* @return array $new New options.
*/
public function update_adsense_option( $new, $prev ) {
if ( $new === $prev ) {
return $new;
}
$content = $this->get_adsense_blog_data( $new );
$this->strategy->add_network_data( self::adsense, $content );
$r = $this->strategy->save_options();
if ( is_wp_error( $r ) ) {
add_settings_error(
'advanced-ads-adsense',
'adsense-ads-txt-created',
$r->get_error_message(),
'error'
);
}
return $new;
}
/**
* Add setting fields.
*
* @param string $hook The slug-name of the settings page.
*/
public function add_settings( $hook ) {
$adsense_data = Advanced_Ads_AdSense_Data::get_instance();
$adsense_id = $adsense_data->get_adsense_id();
add_settings_section(
'advanced_ads_ads_txt_setting_section',
'ads.txt',
[ $this, 'render_ads_txt_section_callback' ],
$hook
);
add_settings_field(
'adsense-ads-txt-enable',
'',
[ $this, 'render_setting_toggle' ],
$hook,
'advanced_ads_ads_txt_setting_section'
);
add_settings_field(
'adsense-ads-txt-content',
'',
[ $this, 'render_setting_additional_content' ],
$hook,
'advanced_ads_ads_txt_setting_section'
);
}
public function render_ads_txt_section_callback() {}
/**
* Render toggle settings.
*/
public function render_setting_toggle() {
global $current_blog;
$domain = isset( $current_blog->domain ) ? $current_blog->domain : '';
$can_process_all_network = $this->can_process_all_network();
$is_all_network = $this->strategy->is_all_network();
$is_enabled = $this->strategy->is_enabled();
include dirname( __FILE__ ) . '/views/setting-create.php';
}
/**
* Render additional content settings.
*/
public function render_setting_additional_content() {
$content = $this->strategy->get_additional_content();
$notices = $this->get_notices();
$notices = $this->get_notices_markup( $notices );
$link = home_url( '/' ) . 'ads.txt';
$adsense_line = $this->get_adsense_blog_data();
include dirname( __FILE__ ) . '/views/setting-additional-content.php';
}
/**
* Check if other sites of the network can be processed by the user.
*
* @return bool
*/
private function can_process_all_network() {
return ! Advanced_Ads_Ads_Txt_Utils::is_subdir()
&& is_super_admin()
&& is_multisite();
}
/**
* Get notices.
*
* @return array Array of notices.
*/
public function get_notices() {
$url = home_url( '/' );
$parsed_url = wp_parse_url( $url );
$notices = [];
if ( ! isset( $parsed_url['scheme'] ) || ! isset ( $parsed_url['host'] ) ) {
return $notices;
}
$link = sprintf( '<a href="%1$s" target="_blank">%1$s</a>', esc_url( $url . 'ads.txt' ) );
$button = ' <button type="button" class="advads-ads-txt-action button" style="vertical-align: middle;" id="%s">%s</button>';
if ( ! $this->strategy->is_enabled() ) {
return $notices;
}
if ( Advanced_Ads_Ads_Txt_Utils::is_subdir() ) {
$notices[] = [
'advads-error-message',
sprintf(
/* translators: %s homepage link */
esc_html__( 'The ads.txt file cannot be placed because the URL contains a subdirectory. You need to make the file available at %s', 'advanced-ads' ),
sprintf( '<a href="%1$s" target="_blank">%1$s</a>', esc_url( $parsed_url['scheme'] . '://' . $parsed_url['host'] ) )
),
];
} else {
if ( null === ( $file = $this->get_notice( 'get_file_info', $url ) ) ) {
$this->notices_are_stale = true;
return $notices;
}
if ( ! is_wp_error( $file ) ) {
if ( $file['exists'] ) {
$notices[] = [
'',
sprintf(
/* translators: %s link of ads.txt */
esc_html__( 'The file is available on %s.', 'advanced-ads' ),
$link
),
];
} else {
$notices[] = [ '', esc_html__( 'The file was not created.', 'advanced-ads' ) ];
}
if ( $file['is_third_party'] ) {
/* translators: %s link */
$message = sprintf( esc_html__( 'A third-party file exists: %s', 'advanced-ads' ), $link );
if ( $this->can_edit_real_file() ) {
$message .= sprintf( $button, 'advads-ads-txt-remove-real', __( 'Import & Replace', 'advanced-ads' ) );
$message .= '<p class="description">'
. __( 'Move the content of the existing ads.txt file into Advanced Ads and remove it.', 'advanced-ads' )
. '</p>';
}
$notices['is_third_party'] = [ 'advads-error-message', $message ];
}
} else {
$notices[] = [
'advads-error-message',
sprintf(
/* translators: %s is replaced with an error message. */
esc_html__( 'An error occured: %s.', 'advanced-ads' ),
esc_html( $file->get_error_message() )
),
];
}
$need_file_on_root_domain = $this->get_notice( 'need_file_on_root_domain', $url );
if ( null === $need_file_on_root_domain ) {
$this->notices_are_stale = true;
return $notices;
}
if ( $need_file_on_root_domain ) {
$notices[] = [
'advads-ads-txt-nfor',
sprintf(
/* translators: %s the line that may need to be added manually */
esc_html__( 'If your site is located on a subdomain, you need to add the following line to the ads.txt file of the root domain: %s', 'advanced-ads' ),
// Without http://.
'<code>subdomain=' . esc_html( $parsed_url['host'] ) . '</code>'
),
];
}
}
return $notices;
}
/**
* Get HTML markup of the notices.
*
* @param array $notices Notices.
* @return string $r HTML markup.
*/
private function get_notices_markup( $notices ) {
if ( $this->notices_are_stale ) {
// Do not print `ul` to fetch notices via AJAX.
return '';
}
$r = '<ul id="advads-ads-txt-notices">';
foreach ( $notices as $notice ) {
$r .= sprintf( '<li class="%s">%s</li>', $notice[0], $notice[1] );
}
$r .= '</ul>';
return $r;
}
/**
* Check if the `ads.txt` file is displayed to visitors.
*
* @return bool True if displayed, False otherwise.
*/
public static function is_displayed() {
$url = home_url( '/' );
$file = self::get_notice( 'get_file_info', $url );
return is_array( $file ) && ! empty( $file['exists'] );
}
/**
* Get a notice.
*
* @return null/bool Boolean on success or null if no cached data exists.
* In the latter case, this function should be called using AJAX
* to get fresh data.
*/
public static function get_notice( $func, $url ) {
if ( ! method_exists( 'Advanced_Ads_Ads_Txt_Utils', $func ) ) {
return false;
}
$url = $url ? $url : home_url( '/' );
$key = self::get_transient_key();
$transient = get_transient( $key );
if ( ! wp_doing_ajax() || ! doing_action( self::ACTION ) ) {
return isset( $transient[ $func ] ) ? $transient[ $func ] : null;
}
$r = call_user_func( [ 'Advanced_Ads_Ads_Txt_Utils', $func ], $url );
$transient = is_array( $transient ) ? $transient : [];
$transient[ $func ] = $r;
set_transient( $key, $transient, WEEK_IN_SECONDS );
return $r;
}
/**
* Get Adsense data.
*
* @param array $new New data.
*
* @return string
*/
public function get_adsense_blog_data( $new = null ) {
if ( null === $new ) {
$new = Advanced_Ads_AdSense_Data::get_instance()->get_options();
}
$adsense_id = ! empty( $new['adsense-id'] ) ? trim( $new['adsense-id'] ) : '';
if ( ! $adsense_id ) {
return '';
}
$data = [
'domain' => 'google.com',
'account_id' => $adsense_id,
'account_type' => 'DIRECT',
'certification_authority' => 'f08c47fec0942fa0',
];
$result = implode( ', ', $data );
return $result;
}
/**
* Check if a third-party ads.txt file exists.
*/
public function ajax_refresh_notices() {
check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
if ( ! Conditional::user_can( 'advanced_ads_manage_options' ) ) {
return;
}
$response = [];
$action_notices = [];
$request_type = Params::request( 'type' );
if ( $request_type ) {
if ( 'remove_real_file' === $request_type ) {
$remove = $this->remove_real_file();
if ( is_wp_error( $remove ) ) {
$action_notices[] = [ 'advads-ads-txt-updated advads-notice-inline advads-error', $remove->get_error_message() ];
} else {
$action_notices[] = [
'advads-ads-txt-updated',
__( 'The ads.txt is now managed with Advanced Ads.', 'advanced-ads' )
];
$options = $this->strategy->get_options();
$response['additional_content'] = esc_textarea( $options['custom'] );
}
}
if ( 'create_real_file' === $request_type ) {
$action_notices[] = $this->create_real_file();
}
}
$notices = $this->get_notices();
$notices = array_merge( $notices, $action_notices );
$response['notices'] = $this->get_notices_markup( $notices );
wp_send_json( $response );
}
/**
* Connect to the filesystem.
*/
private function fs_connect() {
global $wp_filesystem;
$fs_connect = Advanced_Ads_Filesystem::get_instance()->fs_connect( [ ABSPATH ] );
if ( false === $fs_connect || is_wp_error( $fs_connect ) ) {
$message = __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'advanced-ads' );
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
$message = esc_html( $wp_filesystem->errors->get_error_message() );
}
if ( is_wp_error( $fs_connect ) && $fs_connect->get_error_code() ) {
$message = esc_html( $fs_connect->get_error_message() );
}
return new WP_Error( 'can_not_connect', $message );
}
return true;
}
/**
* Remove existing real ads.txt file.
*/
public function remove_real_file() {
global $wp_filesystem;
if ( ! $this->can_edit_real_file() ) {
return new WP_Error( 'not_main_site', __( 'Not the main blog', 'advanced-ads' ) );
}
$fs_connect = $this->fs_connect();
if ( is_wp_error( $fs_connect ) ) {
return $fs_connect;
}
$abspath = trailingslashit( $wp_filesystem->abspath() );
$file = $abspath . 'ads.txt';
if ( $wp_filesystem->exists( $file ) && $wp_filesystem->is_file( $file ) ) {
$data = $wp_filesystem->get_contents( $file );
$tp_file = new Advanced_Ads_Ads_Txt_Real_File();
$tp_file->parse_file( $data );
$aa_data = $this->public->get_frontend_output();
$aa_file = new Advanced_Ads_Ads_Txt_Real_File();
$aa_file->parse_file( $aa_data );
$tp_file->subtract( $aa_file );
$output = $tp_file->output();
$this->strategy->set_additional_content( $output );
$this->strategy->save_options();
if ( $wp_filesystem->delete( $file ) ) {
return true;
} else {
return new WP_Error( 'could_not_delete', __( 'Could not delete the existing ads.txt file', 'advanced-ads' ) );
}
} else {
return new WP_Error( 'not_found', __( 'Could not find the existing ads.txt file', 'advanced-ads' ) );
}
}
/**
* Check if the user is alowed to edit real file.
*/
private function can_edit_real_file() {
return is_super_admin();
}
/**
* Get transient key.
*/
public static function get_transient_key() {
return 'advanced_ads_ads_txt_ctp' . home_url( '/') ;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* View to show the additional content setting for ads.txt.
*
* @package AdvancedAds
* @author Advanced Ads <info@wpadvancedads.com>
*/
if ( $adsense_line ) : ?>
<p>
<?php
echo wp_kses(
sprintf(
/* translators: %s: The adsense line added automically by Advanced Ads. */
__( 'The following line will be added automatically because you connected your AdSense account with Advanced Ads: %s', 'advanced-ads' ),
'<br><code>' . $adsense_line . '</code>'
),
[
'br' => [],
'code' => [],
]
);
?>
</p>
<?php endif; ?>
<br />
<textarea cols="50" rows="5" id="advads-ads-txt-additional-content" name="advads-ads-txt-additional-content"><?php echo esc_textarea( $content ); ?></textarea>
<p class="description"><?php esc_html_e( 'Additional records to add to the file, one record per line. AdSense is added automatically.', 'advanced-ads' ); ?></p>
<div id="advads-ads-txt-notice-wrapper">
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $notices;
?>
</div>
<p class="advads-notice-inline advads-error hidden" id="advads-ads-txt-notice-error">
<?php
/* translators: %s is replaced with an error message. */
esc_html_e( 'An error occured: %s.', 'advanced-ads' );
?>
</p>
<button class="button advads-ads-txt-action" type="button" id="advads-ads-txt-notice-refresh"><?php esc_html_e( 'Check for problems', 'advanced-ads' ); ?></button>
<a href="<?php echo esc_url( $link ); ?>" class="button" target="_blank"><?php esc_html_e( 'Preview', 'advanced-ads' ); ?></button>

View File

@@ -0,0 +1,41 @@
<?php
/**
* View for the ads.txt creation setting.
*
* @package AdvancedAds
*
* @var bool $is_enabled
* @var bool $is_all_network
* @var bool $can_process_all_network
* @var string $domain
*/
?>
<div id="advads-ads-txt">
<label title="<?php esc_html_e( 'enabled', 'advanced-ads' ); ?>">
<input type="radio" name="advads-ads-txt-create" value="1" <?php checked( $is_enabled, true ); ?> />
<?php esc_html_e( 'enabled', 'advanced-ads' ); ?>
</label>
<label title="<?php esc_html_e( 'disabled', 'advanced-ads' ); ?>">
<input type="radio" name="advads-ads-txt-create" value="0" <?php checked( $is_enabled, false ); ?> />
<?php esc_html_e( 'disabled', 'advanced-ads' ); ?>
</label>
<span class="description">
<a target="_blank" href="https://wpadvancedads.com/manual/ads-txt/?utm_source=advanced-ads&utm_medium=link&utm_campaign=settings-ads-txt" class="advads-manual-link">
<?php esc_html_e( 'Manual', 'advanced-ads' ); ?>
</a>
</span>
<?php if ( $can_process_all_network ) : ?>
<p>
<label>
<input name="advads-ads-txt-all-network" type="checkbox"<?php checked( $is_all_network, true ); ?> />
<?php esc_html_e( 'Generate a single ads.txt file for all sites in the multisite network.', 'advanced-ads' ); ?>
</label>
</p>
<p class="description">
<?php esc_html_e( 'Usually, this should be enabled on the main site of the network - often the one without a subdomain or subdirectory.', 'advanced-ads' ); ?>
</p>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,17 @@
<?php
/**
* Module configuration.
*/
$path = dirname( __FILE__ );
return [
'classmap' => [
'Advanced_Ads_Ads_Txt_Public' => $path . '/public/class-advanced-ads-ads-txt-public.php',
'Advanced_Ads_Ads_Txt_Strategy' => $path . '/includes/class-advanced-ads-ads-txt-strategy.php',
'Advanced_Ads_Ads_Txt_Admin' => $path . '/admin/class-advanced-ads-ads-txt-admin.php',
'Advanced_Ads_Ads_Txt_Utils' => $path . '/includes/class-advanced-ads-ads-txt-utils.php',
'Advanced_Ads_Ads_Txt_Real_File'=> $path . '/includes/class-advanced-ads-ads-txt-real-file.php',
],
'textdomain' => null,
];

View File

@@ -0,0 +1,97 @@
<?php
/**
* Represents a real ads.txt file.
*/
class Advanced_Ads_Ads_Txt_Real_File {
private $records = [];
/**
* Parse a real file.
*
* @param string $file File data.
*/
public function parse_file( $file ) {
$lines = preg_split( '/\r\n|\r|\n/', $file );
$comments = [];
foreach ( $lines as $line ) {
$line = explode( '#', $line );
if ( ! empty( $line[1] ) && $comment = trim( $line[1] ) ) {
$comments[] = '# ' . $comment;
}
if ( ! trim( $line[0] ) ) {
continue;
}
$rec = explode( ',', $line[0] );
$data = [];
foreach ( $rec as $k => $r ) {
$r = trim( $r, " \n\r\t," );
if ( $r ) {
$data[] = $r;
}
}
if ( $data ) {
// Add the record and comments that were placed above or to the right of it.
$this->add_record( implode( ', ', $data ), $comments );
}
$comments = [];
}
}
/**
* Add record.
*
* @string $data Record without comments.
* @array $comments Comments related to the record.
*/
private function add_record( $data, $comments = [] ) {
$this->records[] = [ $data, $comments ];
}
/**
* Get records
*
* @return array
*/
public function get_records() {
return $this->records;
}
/**
* Output file
*
* @return string
*/
public function output() {
$r = '';
foreach ( $this->records as $rec ) {
foreach ( $rec[1] as $rec1 ) {
$r .= $rec1 . "\n";
}
$r .= $rec[0] . "\n";
}
return $r;
}
/**
* Subtract another ads.txt file.
*
* @return string
*/
public function subtract( Advanced_Ads_Ads_Txt_Real_File $subtrahend ) {
$r1 = $subtrahend->get_records();
foreach ( $this->records as $k => $record ) {
foreach ( $r1 as $r ) {
if ( $record[0] === $r[0] ) {
unset( $this->records[ $k ] );
}
}
}
}
}

View File

@@ -0,0 +1,220 @@
<?php
/**
* Manage the data.
*/
class Advanced_Ads_Ads_Txt_Strategy {
const OPTION = 'advanced_ads_ads_txt';
private $options;
private $changed = false;
public function __construct() {
$this->options = $this->get_options();
$this->changed = false;
}
/**
* Whether to include records from other sites of the network.
*
* @return bool
*/
public function is_all_network() {
$options = $this->get_options();
return is_multisite() && $options['all_network'];
}
/**
* Is enabled.
*
* @return bool
*/
public function is_enabled() {
$this->options = $this->get_options();
return $this->options['enabled'];
}
/**
* Get additional content.
*
* @return string.
*/
public function get_additional_content() {
$options = $this->get_options();
return $options['custom'];
}
/**
* Toggle the file and add additional conent.
*
* @return bool.
*/
public function toggle( $is_enabled, $all_network, $additional_content ) {
$prev = $this->get_options();
$this->options['enabled'] = $is_enabled;
$this->options['all_network'] = $all_network;
$this->options['custom'] = $additional_content;
if ( $this->options !== $prev ) {
$this->changed = true;
}
return true;
}
/**
* Add ad network data.
*
* @param string $id Ad network id.
* @param string $rec A Record to add.
*
* @return bool
*/
public function add_network_data( $id, $rec ) {
$prev = $this->get_options();
$this->options['networks'][ $id ]['rec'] = $rec;
if ( $this->options !== $prev ) {
$this->changed = true;
}
return true;
}
/**
* Set additional content.
*
* @param string $custom Additional content.
* @param bool $replace Whether to replace or not.
*
* @return bool
*/
public function set_additional_content( $custom, $replace = false ) {
$prev = $this->get_options();
if ( $replace ) {
$this->options['custom'] = $custom;
} else {
$this->options['custom'] .= "\n" . $custom;
}
if ( $this->options !== $prev ) {
$this->changed = true;
}
return true;
}
/**
* Prepare content of a blog for output.
*
* @param array $options Options.
*
* @return string
*/
public function parse_content( $options ) {
$o = '';
foreach ( $options['networks'] as $_id => $data ) {
if ( ! empty( $data['rec'] ) ) {
$o .= $data['rec'] . "\n";
}
}
if ( ! empty( $options['custom'] ) ) {
$o .= $options['custom'] . "\n";
}
return $o;
}
/**
* Save options.
*
* @return bool
*/
public function save_options() {
if ( ! $this->changed ) {
return true;
}
$blog_id = get_current_blog_id();
$tmp_options = Advanced_Ads_Ads_Txt_Utils::remove_duplicate_lines(
[
$blog_id => $this->options,
]
);
$this->options = $tmp_options[ $blog_id ];
if ( is_multisite() ) {
update_site_meta(
get_current_blog_id(),
self::OPTION,
$this->options
);
} else {
update_option(
self::OPTION,
$this->options
);
}
$this->changed = false;
delete_transient( Advanced_Ads_Ads_Txt_Admin::get_transient_key() );
return true;
}
/**
* Get options.
*
* @return array
*/
public function get_options() {
if ( isset( $this->options ) ) {
return $this->options;
}
if ( is_multisite() ) {
$options = get_site_meta( get_current_blog_id(), self::OPTION, true );
} else {
$options = get_option( self::OPTION, [] );
}
if ( ! is_array( $options ) ) {
$options = [];
}
$this->options = $this->load_default_options( $options );
return $this->options;
}
/**
* Load default options.
*
* @param array $options Options.
*
* @return array
*/
public function load_default_options( $options ) {
if ( ! isset( $options['enabled'] ) ) {
$options['enabled'] = true;
}
if ( ! isset( $options['all_network'] ) ) {
$options['all_network'] = false;
}
if ( ! isset( $options['custom'] ) ) {
$options['custom'] = '';
}
if ( ! isset( $options['networks'] ) || ! is_array( $options['networks'] ) ) {
$options['networks'] = [];
}
return $options;
}
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* User interface for managing the 'ads.txt' file.
*/
class Advanced_Ads_Ads_Txt_Utils {
private static $location;
/**
* Get file info.
*
* @param string $url Url to retrieve the file.
* @return array/WP_Error An array containing 'exists', 'is_third_party'.
* A WP_Error upon error.
*/
public static function get_file_info( $url = null ) {
$url = $url ? $url : home_url( '/' );
// Disable ssl verification to prevent errors on servers that are not properly configured with its https certificates.
/** This filter is documented in wp-includes/class-wp-http-streams.php */
$sslverify = apply_filters( 'https_local_ssl_verify', false );
$response = wp_remote_get(
trailingslashit( $url ) . 'ads.txt',
[
'timeout' => 3,
'sslverify' => $sslverify,
'headers' => [
'Cache-Control' => 'no-cache',
],
]
);
$code = wp_remote_retrieve_response_code( $response );
$content = wp_remote_retrieve_body( $response );
$content_type = wp_remote_retrieve_header( $response, 'content-type' );
if ( is_wp_error( $response ) ) {
return $response;
}
$file_exists = ! is_wp_error( $response )
&& 404 !== $code
&& ( false !== stripos( $content_type, 'text/plain' ) );
$header_exists = false !== strpos( $content, Advanced_Ads_Ads_Txt_Public::TOP );
$r = [
'exists' => $file_exists && $header_exists,
'is_third_party' => $file_exists && ! $header_exists
];
return $r;
}
/**
* Check if the another 'ads.txt' file should be hosted on the root domain.
*
* @return bool
*/
public static function need_file_on_root_domain( $url = null ) {
$url = $url ? $url : home_url( '/' );
$parsed_url = wp_parse_url( $url );
if ( ! isset( $parsed_url['host'] ) ) {
return false;
}
$host = $parsed_url['host'];
if ( WP_Http::is_ip_address( $host ) ) {
return false;
}
$host_parts = explode( '.', $host );
$count = count( $host_parts );
if ( $count < 3 ) {
return false;
}
if ( 3 === $count ) {
// Example: `http://one.{net/org/gov/edu/co}.two`.
$suffixes = [ 'net', 'org', 'gov', 'edu', 'co' ];
if ( in_array( $host_parts[ $count - 2 ], $suffixes, true ) ) {
return false;
}
// Example: `one.com.au'.
$suffix_and_tld = implode( '.', array_slice( $host_parts, 1 ) );
if ( in_array( $suffix_and_tld, [ 'com.au', 'com.br', 'com.pl' ] ) ) {
return false;
}
// `http://www.one.two` will only be crawled if `http://one.two` redirects to it.
// Check if such redirect exists.
if ( 'www' === $host_parts[0] ) {
/*
* Do not append `/ads.txt` because otherwise the redirect will not happen.
*/
$no_www_url = $parsed_url['scheme'] . '://' . trailingslashit( $host_parts[1] . '.' . $host_parts[2] );
add_action( 'requests-requests.before_redirect', [ __CLASS__, 'collect_locations' ] );
wp_remote_get( $no_www_url, [ 'timeout' => 5, 'redirection' => 3 ] );
remove_action( 'requests-requests.before_redirect', [ __CLASS__, 'collect_locations' ] );
$no_www_url_parsed = wp_parse_url( self::$location );
if ( isset( $no_www_url_parsed['host'] ) && $no_www_url_parsed['host'] === $host ) {
return false;
}
}
}
return true;
}
/**
* Collect last location.
*
* @return string $location An URL.
*/
public static function collect_locations( $location ) {
self::$location = $location;
}
/**
* Check if the site is in a subdirectory, for example 'http://one.two/three'.
*
* @return bool
*/
public static function is_subdir( $url = null ) {
$url = $url ? $url : home_url( '/' );
$parsed_url = wp_parse_url( $url );
if ( ! empty( $parsed_url['path'] ) && '/' !== $parsed_url['path'] ) {
return true;
}
return false;
}
/**
* Remove duplicate lines.
*
* @param array $blog_data Array of arrays of blog options, keyed by by blog IDs.
* @param array $options {
* Options.
*
* @type string $to_comments Whether to convert duplicate records to comments.
* }
* @return array $blog_data Array of arrays of blog options, keyed by by blog IDs.
*/
public static function remove_duplicate_lines( $blog_data, $options = [] ) {
$to_comments = ! empty( $options['to_comments'] );
$added_records = [];
foreach ( $blog_data as $blog_id => &$blog_options ) {
foreach ( $blog_options['networks'] as $id => $data ) {
// Convert to comments or remove duplicate records that are not comments.
if ( ! empty( $data['rec'] ) && '#' !== substr( $data['rec'], 0, 1 ) && in_array( $data['rec'], $added_records, true ) ) {
if ( $to_comments ) {
$blog_options['networks'][ $id ]['rec'] = '# ' . $blog_options['networks'][ $id ]['rec'];
} else {
unset( $blog_options['networks'][ $id ] );
}
continue;
}
$added_records[] = $data['rec'];
}
$blog_options['custom'] = explode( "\n", $blog_options['custom'] );
$blog_options['custom'] = array_map( 'trim', $blog_options['custom'] );
foreach ( $blog_options['custom'] as $id => $rec ) {
// Convert to comments or remove duplicate records that are not comments.
if ( ! empty( $rec ) && '#' !== substr( $rec, 0, 1 ) && in_array( $rec, $added_records, true ) ) {
if ( $to_comments ) {
$blog_options['custom'][ $id ] = '# ' . $blog_options['custom'][ $id ];
} else {
unset( $blog_options['custom'][ $id ] );
}
continue;
}
$added_records[] = $rec;
}
$blog_options['custom'] = implode( "\n", $blog_options['custom'] );
}
return $blog_data;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Prepare content of the 'ads.txt' file.
*
* @package Advanced_Ads_Ads_Txt
*/
/**
* Initializes the ads.txt module for Advanced Ads plugin.
*
* @return void
*/
function advanced_ads_ads_txt_init(): void {
if (
! is_multisite()
|| ( function_exists( 'is_site_meta_supported' ) && is_site_meta_supported() )
) {
$public = new Advanced_Ads_Ads_Txt_Public( new Advanced_Ads_Ads_Txt_Strategy() );
if ( is_admin() ) {
new Advanced_Ads_Ads_Txt_Admin( new Advanced_Ads_Ads_Txt_Strategy(), $public );
} else {
new Advanced_Ads_Ads_Txt_Public( new Advanced_Ads_Ads_Txt_Strategy() );
}
}
}
add_action( 'advanced-ads-plugin-loaded', 'advanced_ads_ads_txt_init' );

View File

@@ -0,0 +1,172 @@
<?php // phpcs:ignore WordPress.Files.FileName
use AdvancedAds\Framework\Utilities\Params;
/**
* Display the 'ads.txt' file.
*/
class Advanced_Ads_Ads_Txt_Public {
const TOP = '# Advanced Ads ads.txt';
/**
* Ads.txt data management class
*
* @var Advanced_Ads_Ads_Txt_Strategy
*/
private $strategy;
/**
* The Constructor.
*
* @param Advanced_Ads_Ads_Txt_Strategy $strategy Ads.txt data management class.
*/
public function __construct( $strategy ) {
$this->strategy = $strategy;
add_action( 'init', [ $this, 'display' ] );
}
/**
* Display the 'ads.txt' file on the frontend.
*/
public function display() {
$request_uri = filter_var( $_SERVER['REQUEST_URI'] ?? '', FILTER_SANITIZE_URL );
if ( '/ads.txt' === esc_url_raw( $request_uri ) ) {
$content = $this->prepare_frontend_output();
if ( $content ) {
header( 'Content-Type: text/plain; charset=utf-8' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $content;
exit;
}
}
}
/**
* Prepare frontend output.
*
* @return string
*/
public function prepare_frontend_output() {
if (
Advanced_Ads_Ads_Txt_Utils::is_subdir() ||
! $this->strategy->is_enabled()
) {
return;
}
$content = $this->get_frontend_output();
$content = $content ? self::TOP . "\n" . $content : '';
return $content;
}
/**
* Get output for frontend.
*
* @return string
*/
public function get_frontend_output() {
if ( $this->strategy->is_all_network() ) {
$content = $this->prepare_multisite();
} else {
$options = $this->strategy->get_options();
$content = $this->strategy->parse_content( $options );
$content = apply_filters( 'advanced-ads-ads-txt-content', $content, get_current_blog_id() );
}
return $content;
}
/**
* Prepare content of several blogs for output.
*
* @param string $domain Domain name.
* @return string
*/
public function prepare_multisite( $domain = null ) {
global $current_blog, $wpdb;
$domain = $domain ? $domain : $current_blog->domain;
$need_file_on_root_domain = Advanced_Ads_Ads_Txt_Utils::need_file_on_root_domain();
// Get all sites that include the current domain as part of their domains.
$sites = get_sites(
[
'search' => $domain,
'search_columns' => [ 'domain' ],
'meta_key' => Advanced_Ads_Ads_Txt_Strategy::OPTION, // phpcs:ignore
]
);
// Uses `subdomain=` variable.
$referrals = [];
// Included to the ads.txt file of the current domain.
$not_refferals = [];
foreach ( $sites as $site ) {
if ( get_current_blog_id() === (int) $site->blog_id ) {
// Current domain, no need to refer.
$not_refferals[] = $site->blog_id;
continue;
}
if ( $need_file_on_root_domain ) {
// Subdomains cannot refer to other subdomains.
$not_refferals[] = $site->blog_id;
continue;
}
if ( '/' !== $site->path ) {
// We can refer to domains, not domains plus path.
$not_refferals[] = $site->blog_id;
continue;
}
$referrals[ $site->blog_id ] = $site->domain;
}
$o = '';
if ( $not_refferals ) {
$results = $wpdb->get_results( // phpcs:ignore
sprintf(
"SELECT blog_id, meta_value FROM $wpdb->blogmeta WHERE meta_key='%s' AND blog_id IN (%s)",
Advanced_Ads_Ads_Txt_Strategy::OPTION, // phpcs:ignore
join( ',', array_map( 'absint', $not_refferals ) ) // phpcs:ignore
)
);
$blog_data = [];
foreach ( $results as $result ) {
$blog_id = $result->blog_id;
$options = maybe_unserialize( $result->meta_value );
$options = $this->strategy->load_default_options( $options );
$blog_data[ $blog_id ] = $options;
}
$blog_data = Advanced_Ads_Ads_Txt_Utils::remove_duplicate_lines( $blog_data, [ 'to_comments' => true ] );
foreach ( $blog_data as $blog_id => $blog_lines ) {
$content = $this->strategy->parse_content( $blog_lines );
if ( $content ) {
$content = "# blog_id: $blog_id\n" . $content;
}
if ( get_current_blog_id() === $blog_id ) {
// Refer to other subdomains.
foreach ( $referrals as $blog_id => $referral ) {
$content .= "# refer to blog_id: $blog_id\nsubdomain=" . $referral . "\n";
}
}
$content = apply_filters( 'advanced-ads-ads-txt-content', $content, $blog_id );
$o .= $content . "\n";
}
}
return $o;
}
}