This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,999 @@
<?php
/**
* Main WordAds file.
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Status\Host;
define( 'WORDADS_ROOT', __DIR__ );
define( 'WORDADS_BASENAME', plugin_basename( __FILE__ ) );
define( 'WORDADS_FILE_PATH', WORDADS_ROOT . '/' . basename( __FILE__ ) );
define( 'WORDADS_URL', plugins_url( '/', __FILE__ ) );
define( 'WORDADS_API_TEST_ID', '26942' );
define( 'WORDADS_API_TEST_ID2', '114160' );
require_once WORDADS_ROOT . '/php/class-wordads-sidebar-widget.php';
require_once WORDADS_ROOT . '/php/class-wordads-api.php';
require_once WORDADS_ROOT . '/php/class-wordads-cron.php';
require_once WORDADS_ROOT . '/php/class-wordads-california-privacy.php';
require_once WORDADS_ROOT . '/php/class-wordads-ccpa-do-not-sell-link-widget.php';
require_once WORDADS_ROOT . '/php/class-wordads-consent-management-provider.php';
require_once WORDADS_ROOT . '/php/class-wordads-smart.php';
require_once WORDADS_ROOT . '/php/class-wordads-shortcode.php';
/**
* Primary WordAds class.
*/
class WordAds {
/**
* Ads parameters.
*
* @var null
*/
public $params = null;
/**
* Ads.
*
* @var array
*/
public $ads = array();
/**
* Array of supported ad types.
*
* @var array
*/
public static $ad_tag_ids = array(
'mrec' => array(
'tag' => '300x250_mediumrectangle',
'height' => '250',
'width' => '300',
),
'leaderboard' => array(
'tag' => '728x90_leaderboard',
'height' => '90',
'width' => '728',
),
'mobile_leaderboard' => array(
'tag' => '320x50_mobileleaderboard',
'height' => '50',
'width' => '320',
),
'wideskyscraper' => array(
'tag' => '160x600_wideskyscraper',
'height' => '600',
'width' => '160',
),
);
/**
* Mapping array of location slugs to placement ids
*
* @var array
*/
public static $ad_location_ids = array(
'top' => 110,
'belowpost' => 120,
'belowpost2' => 130,
'sidebar' => 140,
'widget' => 150,
'gutenberg' => 200,
'inline' => 310,
'inline-plugin' => 320,
);
/**
* Mapping array of form factor slugs to form factor ids
*
* @var array
*/
public static $form_factor_ids = array(
'square' => '001', // 250x250
'leaderboard' => '002', // 728x90
'skyscraper' => '003', // 120x600
);
/**
* Counter to enable unique, sequential section IDs for all amp-ad units
*
* @var int
*/
public static $amp_section_id = 1;
/**
* Solo unit CSS string.
*
* @var string
*/
public static $solo_unit_css = 'float:left;margin-right:5px;margin-top:0px;';
/**
* Checks for AMP support and returns true iff active & AMP request
*
* @return boolean True if supported AMP request
*
* @since 7.5.0
*/
public static function is_amp() {
return class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request();
}
/**
* Increment the AMP section ID and return the value
*
* @return int
*/
public static function get_amp_section_id() {
return self::$amp_section_id++;
}
/**
* Convenience function for grabbing options from params->options
*
* @param string $option the option to grab.
* @param mixed $default (optional).
* @return mixed option or $default if not set
*
* @since 4.5.0
*/
public function option( $option, $default = false ) {
if ( ! isset( $this->params->options[ $option ] ) ) {
return $default;
}
return $this->params->options[ $option ];
}
/**
* Returns the ad tag property array for supported ad types.
*
* @return array array with ad tags
*
* @since 7.1.0
*/
public function get_ad_tags() {
return self::$ad_tag_ids;
}
/**
* Returns the solo css for unit
*
* @return string the special css for solo units
*
* @since 7.1.0
*/
public function get_solo_unit_css() {
return self::$solo_unit_css;
}
/**
* Instantiate the plugin
*
* @since 4.5.0
*/
public function __construct() {
add_action( 'wp', array( $this, 'init' ) );
add_action( 'rest_api_init', array( $this, 'init' ) );
add_action( 'widgets_init', array( $this, 'widget_callback' ) );
if ( is_admin() ) {
WordAds_California_Privacy::init_ajax_actions();
WordAds_Consent_Management_Provider::init_ajax_actions();
}
}
/**
* Code to run on WordPress 'init' hook
*
* @since 4.5.0
*/
public function init() {
require_once WORDADS_ROOT . '/php/class-wordads-params.php';
$this->params = new WordAds_Params();
if ( $this->should_bail() || self::is_infinite_scroll() ) {
return;
}
if ( is_admin() ) {
require_once WORDADS_ROOT . '/php/class-wordads-admin.php';
return;
}
$this->insert_adcode();
// Include California Privacy Act related features if enabled.
if ( $this->params->options['wordads_ccpa_enabled'] ) {
WordAds_California_Privacy::init();
}
// Initialize CMP if enabled.
if ( isset( $this->params->options['wordads_cmp_enabled'] ) && $this->params->options['wordads_cmp_enabled'] ) {
WordAds_Consent_Management_Provider::init();
}
// Initialize [wordads] shortcode.
WordAds_Shortcode::init();
// Initialize Smart.
WordAds_Smart::instance()->init( $this->params );
if ( ( isset( $_SERVER['REQUEST_URI'] ) && '/ads.txt' === $_SERVER['REQUEST_URI'] )
|| ( site_url( 'ads.txt', 'relative' ) === $_SERVER['REQUEST_URI'] ) ) {
$ads_txt_transient = get_transient( 'wordads_ads_txt' );
if ( false === ( $ads_txt_transient ) ) {
$wordads_ads_txt = WordAds_API::get_wordads_ads_txt();
$ads_txt_transient = is_wp_error( $wordads_ads_txt ) ? '' : $wordads_ads_txt;
set_transient( 'wordads_ads_txt', $ads_txt_transient, DAY_IN_SECONDS );
}
/**
* Provide plugins a way of modifying the contents of the automatically-generated ads.txt file.
*
* @module wordads
*
* @since 6.1.0
*
* @param string WordAds_API::get_wordads_ads_txt() The contents of the ads.txt file.
*/
$ads_txt_content = apply_filters( 'wordads_ads_txt', $ads_txt_transient );
http_response_code( 200 );
header( 'Content-Type: text/plain; charset=utf-8' );
echo esc_html( $ads_txt_content );
die( 0 );
}
}
/**
* Check for Jetpack's The_Neverending_Home_Page and use got_infinity
*
* @return boolean true if load came from infinite scroll
*
* @since 4.5.0
*/
public static function is_infinite_scroll() {
return class_exists( 'The_Neverending_Home_Page' ) && The_Neverending_Home_Page::got_infinity();
}
/**
* Add the actions/filters to insert the ads. Checks for mobile or desktop.
*
* @since 4.5.0
*/
private function insert_adcode() {
add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 10, 2 );
add_action( 'wp_head', array( $this, 'insert_head_meta' ), 20 );
add_action( 'wp_head', array( $this, 'insert_head_iponweb' ), 30 );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_filter( 'wordads_ads_txt', array( $this, 'insert_custom_adstxt' ) );
/**
* Filters enabling ads in `the_content` filter
*
* @see https://jetpack.com/support/ads/
*
* @module wordads
*
* @since 5.8.0
*
* @param bool True to disable ads in `the_content`
*/
if ( ! apply_filters( 'wordads_content_disable', false ) ) {
add_filter( 'the_content', array( $this, 'insert_ad' ) );
}
/**
* Filters enabling ads in `the_excerpt` filter
*
* @see https://jetpack.com/support/ads/
*
* @module wordads
*
* @since 5.8.0
*
* @param bool True to disable ads in `the_excerpt`
*/
if ( ! apply_filters( 'wordads_excerpt_disable', false ) ) {
add_filter( 'the_excerpt', array( $this, 'insert_ad' ) );
}
if ( $this->option( 'enable_header_ad', true ) ) {
if ( self::is_amp() ) {
add_filter( 'the_content', array( $this, 'insert_header_ad_amp' ) );
} else {
add_action( 'wordads_header_ad', array( $this, 'insert_header_ad' ), 100 );
add_action( 'wp_footer', array( $this, 'insert_header_ad' ), 100 );
}
}
}
/**
* Register desktop scripts and styles
*
* @since 4.5.0
*/
public function enqueue_scripts() {
wp_enqueue_style(
'wordads',
WORDADS_URL . 'css/style.css',
array(),
'2015-12-18'
);
}
/**
* Add the IPW resource hints
*
* @since 7.9
*
* @param array $hints Domains for hinting.
* @param string $relation_type Resource type.
*
* @return array Domains for hinting.
*/
public function resource_hints( $hints, $relation_type ) {
if ( 'dns-prefetch' === $relation_type ) {
$hints[] = '//s.pubmine.com';
$hints[] = '//x.bidswitch.net';
$hints[] = '//static.criteo.net';
$hints[] = '//ib.adnxs.com';
$hints[] = '//aax.amazon-adsystem.com';
$hints[] = '//bidder.criteo.com';
$hints[] = '//cas.criteo.com';
$hints[] = '//gum.criteo.com';
$hints[] = '//ads.pubmatic.com';
$hints[] = '//gads.pubmatic.com';
$hints[] = '//tpc.googlesyndication.com';
$hints[] = '//ad.doubleclick.net';
$hints[] = '//googleads.g.doubleclick.net';
$hints[] = '//www.googletagservices.com';
$hints[] = '//cdn.switchadhub.com';
$hints[] = '//delivery.g.switchadhub.com';
$hints[] = '//delivery.swid.switchadhub.com';
}
return $hints;
}
/**
* IPONWEB metadata used by the various scripts
*/
public function insert_head_meta() {
if ( self::is_amp() ) {
return;
}
$hosting_type = ( new Host() )->is_woa_site() ? 1 : 2; // 1 = WPCOM, 2 = Jetpack.
$pagetype = (int) $this->params->get_page_type_ipw();
$data_tags = ( $this->params->cloudflare ) ? ' data-cfasync="false"' : '';
$site_id = $this->params->blog_id;
$consent = (int) isset( $_COOKIE['personalized-ads-consent'] );
$is_logged_in = is_user_logged_in() ? '1' : '0';
$disabled_slot_formats = apply_filters( 'wordads_disabled_slot_formats', array() );
if ( apply_filters( 'wordads_iponweb_bottom_sticky_ad_disable', false ) ) {
$disabled_slot_formats[] = 'MTS';
}
if ( apply_filters( 'wordads_iponweb_sidebar_sticky_right_ad_disable', false ) ) {
$disabled_slot_formats[] = 'DPR';
}
$config = array(
'pt' => $pagetype,
'ht' => $hosting_type,
'tn' => get_stylesheet(),
'uloggedin' => $is_logged_in,
'amp' => false,
'siteid' => $site_id,
'consent' => $consent,
'ad' => array(
'label' => array(
'text' => __( 'Advertisements', 'jetpack' ),
),
'reportAd' => array(
'text' => __( 'Report this ad', 'jetpack' ),
),
'privacySettings' => array(
'text' => __( 'Privacy', 'jetpack' ),
'onClick' => 'js:function() { window.__tcfapi && window.__tcfapi(\'showUi\'); }',
),
),
'disabled_slot_formats' => $disabled_slot_formats,
);
$js_config = WordAds_Array_Utils::array_to_js_object( $config );
?>
<script<?php echo esc_attr( $data_tags ); ?> type="text/javascript">
var __ATA_PP = <?php echo $js_config; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
var __ATA = __ATA || {};
__ATA.cmd = __ATA.cmd || [];
__ATA.criteo = __ATA.criteo || {};
__ATA.criteo.cmd = __ATA.criteo.cmd || [];
</script>
<?php
$section_id = $this->params->blog_id . 5;
// Get below post tag.
$tag_belowpost = $this->get_fallback_ad_snippet( $section_id, 'square', 'belowpost', '', '{{unique_id}}' );
// Remove linebreaks and sanitize.
$tag_belowpost = esc_js( str_replace( array( "\n", "\t", "\r" ), '', $tag_belowpost ) );
// Get an inline tag with a macro as id handled on JS side to use as a fallback.
$tag_inline = $this->get_fallback_ad_snippet( $section_id, 'square', 'inline', '', '{{unique_id}}' );
// Remove linebreaks and sanitize.
$tag_inline = esc_js( str_replace( array( "\n", "\t", "\r" ), '', $tag_inline ) );
// Get top tag.
$tag_top = $this->get_fallback_ad_snippet( $section_id, 'leaderboard', 'top', '', '{{unique_id}}' );
// Remove linebreaks and sanitize.
$tag_top = esc_js( str_replace( array( "\n", "\t", "\r" ), '', $tag_top ) );
// phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
echo <<<HTML
<script type="text/javascript">
var sas_fallback = sas_fallback || [];
sas_fallback.push(
{ tag: "$tag_inline", type: 'inline' },
{ tag: "$tag_belowpost", type: 'belowpost' },
{ tag: "$tag_top", type: 'top' }
);
</script>
HTML;
}
/**
* IPONWEB scripts in <head>
*
* @since 4.5.0
*/
public function insert_head_iponweb() {
if ( self::is_amp() ) {
return;
}
$data_tags = ( $this->params->cloudflare ) ? ' data-cfasync="false"' : '';
?>
<script<?php echo esc_attr( $data_tags ); ?> type="text/javascript">
(function(){var g=Date.now||function(){return+new Date};function h(a,b){a:{for(var c=a.length,d="string"==typeof a?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:"string"==typeof a?a.charAt(b):a[b]};function k(a,b,c){c=null!=c?"="+encodeURIComponent(String(c)):"";if(b+=c){c=a.indexOf("#");0>c&&(c=a.length);var d=a.indexOf("?");if(0>d||d>c){d=c;var e=""}else e=a.substring(d+1,c);a=[a.substr(0,d),e,a.substr(c)];c=a[1];a[1]=b?c?c+"&"+b:b:c;a=a[0]+(a[1]?"?"+a[1]:"")+a[2]}return a};var l=0;function m(a,b){var c=document.createElement("script");c.src=a;c.onload=function(){b&&b(void 0)};c.onerror=function(){b&&b("error")};a=document.getElementsByTagName("head");var d;a&&0!==a.length?d=a[0]:d=document.documentElement;d.appendChild(c)}function n(a){var b=void 0===b?document.cookie:b;return(b=h(b.split("; "),function(c){return-1!=c.indexOf(a+"=")}))?b.split("=")[1]:""}function p(a){return"string"==typeof a&&0<a.length}
function r(a,b,c){b=void 0===b?"":b;c=void 0===c?".":c;var d=[];Object.keys(a).forEach(function(e){var f=a[e],q=typeof f;"object"==q&&null!=f||"function"==q?d.push(r(f,b+e+c)):null!==f&&void 0!==f&&(e=encodeURIComponent(b+e),d.push(e+"="+encodeURIComponent(f)))});return d.filter(p).join("&")}function t(a,b){a||((window.__ATA||{}).config=b.c,m(b.url))}var u=Math.floor(1E13*Math.random()),v=window.__ATA||{};window.__ATA=v;window.__ATA.cmd=v.cmd||[];v.rid=u;v.createdAt=g();var w=window.__ATA||{},x="s.pubmine.com";
w&&w.serverDomain&&(x=w.serverDomain);var y="//"+x+"/conf",z=window.top===window,A=window.__ATA_PP&&window.__ATA_PP.gdpr_applies,B="boolean"===typeof A?Number(A):null,C=window.__ATA_PP||null,D=z?document.referrer?document.referrer:null:null,E=z?window.location.href:document.referrer?document.referrer:null,F,G=n("__ATA_tuuid");F=G?G:null;var H=window.innerWidth+"x"+window.innerHeight,I=n("usprivacy"),J=r({gdpr:B,pp:C,rid:u,src:D,ref:E,tuuid:F,vp:H,us_privacy:I?I:null},"",".");
(function(a){var b=void 0===b?"cb":b;l++;var c="callback__"+g().toString(36)+"_"+l.toString(36);a=k(a,b,c);window[c]=function(d){t(void 0,d)};m(a,function(d){d&&t(d)})})(y+"?"+J);}).call(this);
</script>
<?php
}
/**
* Insert the ad onto the page
*
* @since 4.5.0
*
* @param string $content HTML content.
*/
public function insert_ad( $content ) {
// Don't insert ads in feeds, or for anything but the main display. (This is required for compatibility with the Publicize module).
if ( is_feed() || ! is_main_query() || ! in_the_loop() ) {
return $content;
}
/**
* Allow third-party tools to disable the display of in post ads.
*
* @module wordads
*
* @since 4.5.0
*
* @param bool true Should the in post unit be disabled. Default to false.
*/
$disable = apply_filters( 'wordads_inpost_disable', false );
if ( ! $this->params->should_show() || $disable ) {
return $content;
}
$ad_type = $this->option( 'wordads_house' ) ? 'house' : 'iponweb';
return $content . $this->get_ad( 'belowpost', $ad_type );
}
/**
* Insert an inline ad into a post content
* Used for rendering the `wordads` shortcode.
*
* @since 6.1.0
*
* @param string $content HTML content.
*/
public function insert_inline_ad( $content ) {
// Ad JS won't work in XML feeds.
if ( is_feed() ) {
return $content;
}
/**
* Allow third-party tools to disable the display of in post ads.
*
* @module wordads
*
* @since 4.5.0
*
* @param bool true Should the in post unit be disabled. Default to false.
*/
$disable = apply_filters( 'wordads_inpost_disable', false );
if ( $disable ) {
return $content;
}
$ad_type = $this->option( 'wordads_house' ) ? 'house' : 'iponweb';
$location = 'shortcode';
// Not house ad and WATL enabled.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( 'house' !== $ad_type && ( isset( $_GET['wordads-logging'] ) && isset( $_GET[ $location ] ) && 'true' === $_GET[ $location ] ) ) {
return $content . $this->get_watl_ad_html_tag( $location );
}
return $content . $this->get_ad( 'inline', $ad_type );
}
/**
* Inserts ad into header
*
* @since 4.5.0
*/
public function insert_header_ad() {
/**
* Allow third-party tools to disable the display of header ads.
*
* @module wordads
*
* @since 4.5.0
*
* @param bool true Should the header unit be disabled. Default to false.
*/
if ( apply_filters( 'wordads_header_disable', false ) ) {
return;
}
// Prevent multiple manual ads.
if ( 2 <= did_action( 'wordads_header_ad' ) ) {
return;
}
// Prevent placing an automatic ad if a manual ad has already been placed.
if ( doing_action( 'wp_footer' ) && did_action( 'wordads_header_ad' ) ) {
return;
}
// Prevent placing a manual ad if an automatic ad has already been placed.
if ( doing_action( 'wordads_header_ad' ) && did_action( 'wp_footer' ) ) {
return;
}
// Default ad placement to just after the <body> tag.
$selector = 'body > :first-child';
// Special theme cases.
switch ( get_stylesheet() ) {
case 'twentyseventeen':
$selector = '#content';
break;
case 'twentyfifteen':
$selector = '#main';
break;
case 'twentyfourteen':
$selector = 'article';
break;
}
// Don't relocate if the ad is being placed manually.
if ( doing_action( 'wordads_header_ad' ) ) {
$selector = '';
}
$section_id = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID : $this->params->blog_id . '2';
$form_factor = $this->params->mobile_device ? 'square' : 'leaderboard';
echo $this->get_dynamic_ad_snippet( $section_id, $form_factor, 'top', $selector ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Header unit for AMP
*
* @param string $content Content of the page.
*
* @since 7.5.0
*/
public function insert_header_ad_amp( $content ) {
$ad_type = $this->option( 'wordads_house' ) ? 'house' : 'iponweb';
if ( 'house' === $ad_type ) {
return $content;
}
return $this->get_ad( 'top_amp', $ad_type ) . $content;
}
/**
* Filter the latest ads.txt to include custom user entries. Strips any tags or whitespace.
*
* @param string $adstxt The ads.txt being filtered.
* @return string Filtered ads.txt with custom entries, if applicable.
*
* @since 6.5.0
*/
public function insert_custom_adstxt( $adstxt ) {
if ( ! $this->option( 'wordads_custom_adstxt_enabled' ) ) {
return $adstxt;
}
$custom_adstxt = trim( wp_strip_all_tags( $this->option( 'wordads_custom_adstxt' ) ) );
if ( $custom_adstxt ) {
$adstxt .= "\n\n#Jetpack - User Custom Entries\n";
$adstxt .= $custom_adstxt . "\n";
}
return $adstxt;
}
/**
* Get the ad for the spot and type.
*
* @param string $spot top, side, inline, or belowpost.
* @param string $type iponweb or adsense.
*/
public function get_ad( $spot, $type = 'iponweb' ) {
$snippet = '';
if ( 'iponweb' === $type ) {
$section_id = WORDADS_API_TEST_ID;
$snippet = '';
if ( 'top' === $spot ) {
// mrec for mobile, leaderboard for desktop.
$section_id = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID : $this->params->blog_id . '2';
$form_factor = $this->params->mobile_device ? 'square' : 'leaderboard';
$snippet = $this->get_dynamic_ad_snippet( $section_id, $form_factor, $spot );
} elseif ( 'belowpost' === $spot ) {
$section_id = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID : $this->params->blog_id . '1';
$snippet = $this->get_dynamic_ad_snippet( $section_id, 'square', $spot );
} elseif ( 'inline' === $spot ) {
$section_id = 0 === $this->params->blog_id ? WORDADS_API_TEST_ID : $this->params->blog_id . '5';
$snippet = $this->get_dynamic_ad_snippet( $section_id, 'square', $spot );
} elseif ( 'top_amp' === $spot ) {
// Ad unit which can safely be inserted below title, above content in a variety of themes.
$width = 300;
$height = 250;
$snippet = $this->get_ad_div( $spot, $this->get_amp_snippet( $height, $width ) );
}
} elseif ( 'house' === $type ) {
$leaderboard = 'top' === $spot && ! $this->params->mobile_device;
$snippet = $this->get_house_ad( $leaderboard ? 'leaderboard' : 'mrec' );
if ( 'belowpost' === $spot && $this->option( 'wordads_second_belowpost', true ) ) {
$snippet .= $this->get_house_ad( $leaderboard ? 'leaderboard' : 'mrec' );
}
}
return $snippet;
}
/**
* Returns the AMP snippet to be inserted
*
* @param int $height Height.
* @param int $width Width.
* @return string
*
* @since 8.7
*/
public function get_amp_snippet( $height, $width ) {
$height = esc_attr( $height + 15 ); // this will ensure enough padding for "Report this ad".
$width = esc_attr( $width );
$amp_section_id = esc_attr( self::get_amp_section_id() );
$site_id = esc_attr( $this->params->blog_id );
return <<<HTML
<amp-ad width="$width" height="$height"
type="pubmine"
data-siteid="$site_id"
data-section="$amp_section_id">
</amp-ad>
HTML;
}
/**
* Compatibility function -- main functionality replaced with get_dynamic_ad_snippet
*
* @param int $section_id Ad section.
* @param int $height Ad height.
* @param int $width Ad width.
* @param string $location Location.
* @param string $css CSS.
*
* @return string
*
* @since 5.7
*/
public function get_ad_snippet( $section_id, $height, $width, $location = '', $css = '' ) {
if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
return $this->get_amp_snippet( $height, $width );
}
$this->ads[] = array(
'location' => $location,
'width' => $width,
'height' => $height,
);
if ( 'gutenberg' === $location ) {
$ad_number = count( $this->ads ) . '-' . uniqid();
$data_tags = $this->params->cloudflare ? ' data-cfasync="false"' : '';
$css = esc_attr( $css );
$loc_id = 100;
if ( ! empty( self::$ad_location_ids[ $location ] ) ) {
$loc_id = self::$ad_location_ids[ $location ];
}
$loc_id = esc_js( $loc_id );
return <<<HTML
<div style="padding-bottom:15px;width:{$width}px;height:{$height}px;$css">
<div id="atatags-{$ad_number}">
<script$data_tags type="text/javascript">
__ATA.cmd.push(function() {
__ATA.initSlot('atatags-{$ad_number}', {
collapseEmpty: 'before',
sectionId: '{$section_id}',
location: {$loc_id},
width: {$width},
height: {$height}
});
});
</script>
</div>
</div>
HTML;
}
$form_factor = 'square';
if ( 250 > $width ) {
$form_factor = 'skyscraper';
} elseif ( 300 < $width ) {
$form_factor = 'leaderboard';
}
return $this->get_dynamic_ad_snippet( $section_id, $form_factor, $location );
}
/**
* Returns the dynamic snippet to be inserted into the ad unit
*
* @param int $section_id section_id.
* @param string $form_factor form_factor.
* @param string $location location.
* @param string $relocate location to be moved after the fact for themes without required hook.
* @param string | null $id A unique string ID or placeholder.
*
* @return string
*
* @since 8.7
*/
public function get_dynamic_ad_snippet( $section_id, $form_factor = 'square', $location = '', $relocate = '', $id = null ) {
// Allow overriding and printing of the tag parsed by the WATL.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$is_location_enabled = isset( $_GET['wordads-logging'] ) && isset( $_GET[ $location ] ) && 'true' === $_GET[ $location ];
if ( ( 'top' === $location || 'belowpost' === $location ) && $is_location_enabled ) {
return self::get_watl_ad_html_tag( $location );
}
return $this->get_fallback_ad_snippet( $section_id, $form_factor, $location, $relocate, $id );
}
/**
* Returns the fallback dynamic snippet to be inserted into the ad unit
*
* @param int $section_id section_id.
* @param string $form_factor form_factor.
* @param string $location location.
* @param string $relocate location to be moved after the fact for themes without required hook.
* @param string | null $id A unique string ID or placeholder.
*
* @return string
*
* @since 8.7
*/
public function get_fallback_ad_snippet( $section_id, $form_factor = 'square', $location = '', $relocate = '', $id = null ) {
$div_id = 'atatags-' . $section_id . '-' . ( $id ?? uniqid() );
$div_id = esc_attr( $div_id );
// Default form factor.
$form_factor_id = self::$form_factor_ids['square'];
if ( isset( self::$form_factor_ids[ $form_factor ] ) ) {
$form_factor_id = self::$form_factor_ids[ $form_factor ];
}
$loc_id = 100;
if ( isset( self::$ad_location_ids[ $location ] ) ) {
$loc_id = self::$ad_location_ids[ $location ];
}
$form_factor_id = esc_js( $form_factor_id );
$advertisements_text = esc_js( __( 'Advertisements', 'jetpack' ) );
$report_ad_text = esc_js( __( 'Report this ad', 'jetpack' ) );
$privacy_settings_text = esc_js( __( 'Privacy settings', 'jetpack' ) );
$relocate_script = '';
if ( ! empty( $relocate ) ) {
$selector = wp_json_encode( $relocate );
$relocate_script = <<<JS
<script type="text/javascript">
var adNode = document.getElementById( '{$div_id}' );
var selector = {$selector};
var relocateNode = document.querySelector( selector );
relocateNode.parentNode.insertBefore( adNode, relocateNode );
</script>
JS;
}
return <<<HTML
<div id="{$div_id}"></div>
{$relocate_script}
<script>
__ATA.cmd.push(function() {
__ATA.initDynamicSlot({
id: '{$div_id}',
location: {$loc_id},
formFactor: '{$form_factor_id}',
label: {
text: '{$advertisements_text}',
},
creative: {
reportAd: {
text: '{$report_ad_text}',
},
privacySettings: {
text: '{$privacy_settings_text}',
onClick: function() { window.__tcfapi && window.__tcfapi('showUi'); },
}
}
});
});
</script>
HTML;
}
/**
* Returns the complete ad div with snippet to be inserted into the page
*
* @param string $spot top, side, inline, or belowpost.
* @param string $snippet The snippet to insert into the div.
* @param array $css_classes CSS classes.
* @return string The supporting ad unit div.
*
* @since 7.1
*/
public function get_ad_div( $spot, $snippet, array $css_classes = array() ) {
if ( strpos( strtolower( $spot ), 'amp' ) === false && ! 'inline' === $spot ) {
return $snippet; // we don't want dynamic ads to be inserted for AMP & Gutenberg.
}
if ( empty( $css_classes ) ) {
$css_classes = array();
}
$css_classes[] = 'wpcnt';
if ( 'top' === $spot ) {
$css_classes[] = 'wpcnt-header';
}
$spot = esc_attr( $spot );
$classes = esc_attr( implode( ' ', $css_classes ) );
$about = esc_html__( 'Advertisements', 'jetpack' );
return <<<HTML
<div class="$classes">
<div class="wpa">
<span class="wpa-about">$about</span>
<div class="u $spot">
$snippet
</div>
</div>
</div>
HTML;
}
/**
* Check the reasons to bail before we attempt to insert ads.
*
* @return true if we should bail (don't insert ads)
*
* @since 4.5.0
*/
public function should_bail() {
return ! $this->option( 'wordads_approved' ) || (bool) $this->option( 'wordads_unsafe' );
}
/**
* Returns markup for HTML5 house ad base on unit
*
* @param string $unit mrec, widesky, or leaderboard.
* @return string markup for HTML5 house ad
*
* @since 4.7.0
*/
public function get_house_ad( $unit = 'mrec' ) {
switch ( $unit ) {
case 'widesky':
$width = 160;
$height = 600;
break;
case 'leaderboard':
$width = 728;
$height = 90;
break;
case 'mrec':
default:
$width = 300;
$height = 250;
break;
}
return <<<HTML
<iframe
src="https://s0.wp.com/wp-content/blog-plugins/wordads/house/html5/$unit/index.html"
width="$width"
height="$height"
frameborder="0"
scrolling="no"
marginheight="0"
marginwidth="0">
</iframe>
HTML;
}
/**
* Returns the html ad tag used by WordAds Tag Library
*
* @param string $slot_type e.g belowpost, gutenberg_rectangle.
*
* @return string
*
* @since 8.7
*/
public static function get_watl_ad_html_tag( string $slot_type ): string {
return "<div class=\"wordads-tag\" data-slot-type=\"$slot_type\" style=\"display: none;\"></div>";
}
/**
* Activation hook actions
*
* @since 4.5.0
*/
public static function activate() {
WordAds_API::update_wordads_status_from_api();
}
/**
* Registers the widgets.
*/
public function widget_callback() {
register_widget( 'WordAds_Sidebar_Widget' );
$ccpa_enabled = get_option( 'wordads_ccpa_enabled' );
if ( $ccpa_enabled ) {
register_widget( 'WordAds_Ccpa_Do_Not_Sell_Link_Widget' );
}
}
}
add_action( 'jetpack_activate_module_wordads', array( 'WordAds', 'activate' ) );
add_action( 'jetpack_activate_module_wordads', array( 'WordAds_Cron', 'activate' ) );
add_action( 'jetpack_deactivate_module_wordads', array( 'WordAds_Cron', 'deactivate' ) );
global $wordads;
$wordads = new WordAds();
@@ -0,0 +1,63 @@
/**
HTML markup structure of an ad:
<div class="wpcnt">
<div class="wpa [wpmrec|wpwidesky|wpleaderboard]">
<a class="wpa-about" href="http://wordpress.com/about-these-ads/" rel="nofollow">
About these ads
</a>
<div class="u">
[ad unit here]
</div>
</div>
</div>
*/
/* outer container */
.wpcnt {
text-align: center;
line-height: 2;
}
/* inner container */
.wpa {
position: relative;
overflow: hidden; /* this hides "about these ads" when there's no adfill */
display: inline-block;
max-width: 100%; /* important! this bit of CSS will *crop* any ad that's larger than the parent container! */
}
/* about these ads */
.wpa-about {
position: absolute;
top: 5px;
left: 0;
right: 0;
display: block;
margin-top: 0;
color: #888;
font: 10px/1 "Open Sans", Arial, sans-serif !important;
text-align: left !important;
text-decoration: none !important;
opacity: 0.85;
border-bottom: none !important; /* some themes ad dotted underlines, that won't look nice */
box-shadow: none !important;
}
/* ad unit wrapper */
.wpa .u>div { /* @todo: deprecate wpdvert */
display: block;
margin-top: 5px; /* this makes "about these ads" visible */
margin-bottom: 1em; /* every ad should have a little space below it */
}
div.wpa>div {
margin-top: 20px;
}
.wpa .u .adsbygoogle {
display: block;
margin-top: 17px; /* this makes "about these ads" visible */
margin-bottom: 1em; /* every ad should have a little space below it */
background-color: transparent;
}
@@ -0,0 +1,21 @@
function a8c_adflow_callback( data ) {
if ( data && data.scripts && Array.isArray( data.scripts ) ) {
if ( data.config ) {
let configurationScript = document.createElement( 'script' );
configurationScript.id = 'adflow-configuration';
configurationScript.type = 'application/configuration';
configurationScript.innerHTML = JSON.stringify( data.config );
// Add the adflow-configuration script element to the document's body.
document.head.appendChild( configurationScript );
}
// Load each adflow script.
data.scripts.forEach( function ( scriptUrl ) {
let script = document.createElement( 'script' );
script.src = scriptUrl;
document.head.appendChild( script );
} );
}
}
window.a8c_adflow_callback = a8c_adflow_callback;
@@ -0,0 +1,21 @@
function a8c_cmp_callback( data ) {
if ( data && data.scripts && Array.isArray( data.scripts ) ) {
if ( data.config ) {
let configurationScript = document.createElement( 'script' );
configurationScript.id = 'cmp-configuration';
configurationScript.type = 'application/configuration';
configurationScript.innerHTML = JSON.stringify( data.config );
// Add the cmp-configuration script element to the document's body
document.head.appendChild( configurationScript );
}
// Load each cmp script
data.scripts.forEach( function ( scriptUrl ) {
let script = document.createElement( 'script' );
script.src = scriptUrl;
document.head.appendChild( script );
} );
}
}
window.a8c_cmp_callback = a8c_cmp_callback;
@@ -0,0 +1,355 @@
/**
* Outputs Javascript to handle California IP detection, consent modal, and setting of default cookies.
*/
( function () {
/* global ccpaSettings */
// Minimal Mozilla Cookie library.
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie/Simple_document.cookie_framework
var cookieLib = {
getItem: function ( e ) {
return (
( e &&
decodeURIComponent(
document.cookie.replace(
new RegExp(
'(?:(?:^|.*;)\\s*' +
encodeURIComponent( e ).replace( /[-.+*]/g, '\\$&' ) +
'\\s*\\=\\s*([^;]*).*$)|^.*$'
),
'$1'
)
) ) ||
null
);
},
setItem: function ( e, o, n, t, r, i ) {
if ( ! e || /^(?:expires|max-age|path|domain|secure)$/i.test( e ) ) {
return ! 1;
}
var c = '';
if ( n ) {
switch ( n.constructor ) {
case Number:
c = n === 1 / 0 ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + n;
break;
case String:
c = '; expires=' + n;
break;
case Date:
c = '; expires=' + n.toUTCString();
}
}
return (
( 'rootDomain' !== r && '.rootDomain' !== r ) ||
( r =
( '.rootDomain' === r ? '.' : '' ) +
document.location.hostname.split( '.' ).slice( -2 ).join( '.' ) ),
( document.cookie =
encodeURIComponent( e ) +
'=' +
encodeURIComponent( o ) +
c +
( r ? '; domain=' + r : '' ) +
( t ? '; path=' + t : '' ) +
( i ? '; secure' : '' ) ),
! 0
);
},
};
// Implement IAB USP API.
window.__uspapi = function ( command, version, callback ) {
// Validate callback.
if ( typeof callback !== 'function' ) {
return;
}
// Validate the given command.
if ( command !== 'getUSPData' || version !== 1 ) {
callback( null, false );
return;
}
// Check for GPC. If set, override any stored cookie.
if ( navigator.globalPrivacyControl ) {
callback( { version: 1, uspString: '1YYN' }, true );
return;
}
// Check for cookie.
var consent = cookieLib.getItem( 'usprivacy' );
// Invalid cookie.
if ( null === consent ) {
callback( null, false );
return;
}
// Everything checks out. Fire the provided callback with the consent data.
callback( { version: 1, uspString: consent }, true );
};
var setDefaultOptInCookie = function () {
var value = ccpaSettings.defaultOptInCookieString;
var domain =
'.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
cookieLib.setItem( 'usprivacy', value, 365 * 24 * 60 * 60, '/', domain );
};
var setDefaultOptOutCookie = function () {
var value = ccpaSettings.defaultOptOutCookieString;
var domain =
'.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
cookieLib.setItem( 'usprivacy', value, 24 * 60 * 60, '/', domain );
};
var setDefaultNotApplicableCookie = function () {
var value = '1---';
var domain =
'.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
cookieLib.setItem( 'usprivacy', value, 24 * 60 * 60, '/', domain );
};
var setCcpaAppliesCookie = function ( value ) {
var domain =
'.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
cookieLib.setItem( 'ccpa_applies', value, 24 * 60 * 60, '/', domain );
};
var injectLoadingMessage = function () {
var wrapper = document.createElement( 'div' );
document.body.insertBefore( wrapper, document.body.firstElementChild );
wrapper.outerHTML =
'<div id="ccpa-loading" class="cleanslate ccpa__loading-wrapper">' +
'<div class="ccpa__loading-overlay">' +
'<span class="ccpa__loading-message">' +
ccpaSettings.strings.pleaseWait +
'...</span>' +
'</div>' +
'</div>';
};
var destroyModal = function () {
var node = document.querySelector( '#ccpa-modal' );
if ( node ) {
node.parentElement.removeChild( node );
}
};
var injectModal = function () {
destroyModal();
injectLoadingMessage();
var request = new XMLHttpRequest();
request.open(
'GET',
ccpaSettings.ajaxUrl + '?action=privacy_optout_markup&security=' + ccpaSettings.ajaxNonce,
true
);
request.onreadystatechange = function () {
if ( 4 === this.readyState ) {
if ( 200 === this.status ) {
document.getElementById( 'ccpa-loading' ).remove();
var wrapper = document.createElement( 'div' );
document.body.insertBefore( wrapper, document.body.firstElementChild );
wrapper.outerHTML = this.response;
document.getElementById( 'ccpa-opt-out' ).focus();
var optOut = document.querySelector( '#ccpa-modal .opt-out' );
optOut.addEventListener( 'click', function ( e ) {
var post = new XMLHttpRequest();
post.open( 'POST', ccpaSettings.ajaxUrl, true );
post.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded; charset=UTF-8'
);
post.onreadystatechange = function () {
if ( 4 === this.readyState ) {
if ( 200 === this.status ) {
var result = JSON.parse( this.response );
if ( result && result.success ) {
// Note: Cooke is set in HTTP response from POST, so only need to update the toggle switch state.
if ( result.data ) {
e.target.parentNode.classList.add( 'is-checked' );
e.target.parentNode.parentNode.classList.add( 'is-checked' );
} else {
e.target.parentNode.classList.remove( 'is-checked' );
e.target.parentNode.parentNode.classList.remove( 'is-checked' );
}
}
}
}
};
post.send(
'action=privacy_optout&optout=' +
e.target.checked +
'&security=' +
ccpaSettings.ajaxNonce
);
} );
// Set initial toggle status based on cookie data.
var usprivacyCookie = cookieLib.getItem( 'usprivacy' );
var optout = usprivacyCookie && 'Y' === usprivacyCookie[ 2 ];
var toggle = document.querySelector( '#ccpa-modal .opt-out' );
toggle.checked = optout;
if ( optout ) {
toggle.parentNode.classList.add( 'is-checked' );
toggle.parentNode.parentNode.classList.add( 'is-checked' );
}
var buttons = document.querySelectorAll( '#ccpa-modal .components-button' );
Array.prototype.forEach.call( buttons, function ( el ) {
el.addEventListener( 'click', function () {
destroyModal();
} );
} );
}
}
};
request.send();
};
var dispatchInitializedEvent = function ( ccpaApplies ) {
// Dispatches a custom event with data indicating if the CCPA applies or not once it has been determined.
// Sites can listen for this event and do additional processing, e.g. showing or hiding additional elements.
var event = document.createEvent( 'CustomEvent' );
event.initCustomEvent( 'wordads-ccpa-initialized', true, false, { ccpaApplies: ccpaApplies } );
document.dispatchEvent( event );
};
var initialize = function ( ccpaApplies, usprivacyCookie ) {
// Get any Do Not Sell links on the page.
var dnsLinks = document.querySelectorAll( '.ccpa-do-not-sell' );
// No usprivacy cookie, so we need to set it.
if ( null === usprivacyCookie ) {
if ( ccpaApplies ) {
if ( 0 === dnsLinks.length ) {
// Could not find a Do Not Sell link as required, so default to opt-OUT just to be safe.
setDefaultOptOutCookie();
} else {
// Found a Do Not Sell link, so set default opt-in.
setDefaultOptInCookie();
}
} else {
// CCPA does not apply.
setDefaultNotApplicableCookie();
}
}
// If CCPA does not apply, and we are not overriding it for admins, then we can stop here.
if ( ! ccpaApplies && 'false' === ccpaSettings.forceApplies ) {
dispatchInitializedEvent( false );
return;
}
// Displays Do Not Sell links and adds handlers to display the modal when clicked.
Array.prototype.forEach.call( dnsLinks, function ( dnsLink ) {
dnsLink.addEventListener( 'click', function ( e ) {
e.preventDefault();
if ( ! ccpaSettings.stylesLoaded ) {
// Load wordads-ccpa.min.css.
var ccpaCss = document.createElement( 'link' );
ccpaCss.rel = 'stylesheet';
ccpaCss.type = 'text/css';
ccpaCss.href = ccpaSettings.ccpaCssUrl;
document.getElementsByTagName( 'HEAD' )[ 0 ].appendChild( ccpaCss );
ccpaSettings.stylesLoaded = true;
}
injectModal();
} );
// Make the link visible.
dnsLink.style.display = '';
} );
// CCPA applies (or we're forcing it to display for admins). Let any listeners know.
dispatchInitializedEvent( true );
};
// Setup CCPA on DOM loaded.
document.addEventListener( 'DOMContentLoaded', function () {
// Look for usprivacy cookies first.
var usprivacyCookie = cookieLib.getItem( 'usprivacy' );
// Found a usprivacy cookie.
if ( null !== usprivacyCookie ) {
// CCPA does not apply.
if ( '1---' === usprivacyCookie ) {
initialize( false, usprivacyCookie );
} else {
// CCPA applies.
initialize( true, usprivacyCookie );
}
// No more processing needed.
return;
}
// We don't have a usprivacy cookie, so check to see if we have a CCPA applies cookie.
var ccpaCookie = cookieLib.getItem( 'ccpa_applies' );
// No CCPA applies cookie found, so we'll need to geolocate if this visitor is from applicable US state.
// This needs to happen client side because we do not have region geo data in our $SERVER headers,
// only country data -- therefore we can't vary cache on the region.
if ( null === ccpaCookie ) {
var request = new XMLHttpRequest();
request.open( 'GET', 'https://public-api.wordpress.com/geo/', true );
request.onreadystatechange = function () {
if ( 4 === this.readyState ) {
if ( 200 === this.status ) {
// Got a geo response. Parse out the region data.
var data = JSON.parse( this.response );
var region = data.region ? data.region.toLowerCase() : '';
var ccpaApplies =
[
'california',
'colorado',
'connecticut',
'delaware',
'indiana',
'iowa',
'montana',
'new jersey',
'oregon',
'tennessee',
'texas',
'utah',
'virginia',
].indexOf( region ) > -1;
// Set CCPA applies cookie. This keeps us from having to make a geo request too frequently.
setCcpaAppliesCookie( ccpaApplies );
// Perform the rest of the initialization.
initialize( ccpaApplies, null );
} else {
// Geolocation request failed, so default to CCPA applies just to be safe.
setCcpaAppliesCookie( true );
// Perform the rest of the initialization.
initialize( true, null );
}
}
};
// Send the geo request.
request.send();
} else {
// We found a CCPA applies cookie. Continue with initialization.
initialize( 'true' === ccpaCookie, null );
}
} );
} )();
@@ -0,0 +1,79 @@
<?php
/**
* WordAds Admin.
*
* @package automattic/jetpack
*/
/**
* The standard set of admin pages for the user if Jetpack is installed
*/
class WordAds_Admin {
/**
* WordAds_Admin Constructor.
*
* @since 4.5.0
*/
public function __construct() {
if ( current_user_can( 'manage_options' ) && isset( $_GET['ads_debug'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
WordAds_API::update_wordads_status_from_api();
add_action( 'admin_notices', array( $this, 'debug_output' ) );
}
}
/**
* Output the API connection debug
*
* @since 4.5.0
*/
public function debug_output() {
global $wordads, $wordads_status_response;
$response = $wordads_status_response;
if ( empty( $response ) ) {
$response = 'No response from API :(';
} else {
$response = print_r( $response, 1 ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
}
$status = $wordads->option( 'wordads_approved' ) ?
array(
'color' => 'green',
'approved' => 'Yes',
) :
array(
'color' => 'red',
'approved' => 'No',
);
$type = $wordads->option( 'wordads_approved' ) ? 'updated' : 'error';
$message = sprintf(
wp_kses(
/* Translators: %1$s is the status color, %2$s is the status, %3$s is the response */
__( '<p>Status: <span style="color:%1$s;">%2$s</span></p><pre>%3$s</pre>', 'jetpack' ),
array(
'p' => array(),
'span' => array(
'style' => array(),
),
'pre' => array(),
)
),
esc_attr( $status['color'] ),
esc_html( $status ),
esc_html( $response )
);
wp_admin_notice(
$message,
array(
'type' => $type,
'dismissible' => true,
'paragraph_wrap' => false,
)
);
}
}
global $wordads_admin;
$wordads_admin = new WordAds_Admin();
@@ -0,0 +1,98 @@
<?php
/**
* The WordAds API.
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Status;
/**
* Methods for accessing data through the WPCOM REST API
*
* @since 4.5.0
*/
class WordAds_API {
/**
* Get the site's WordAds status
*
* @return array|WP_Error Array of site status values, or WP_Error if no response from the API.
*
* @since 4.5.0
*/
public static function get_wordads_status() {
global $wordads_status_response;
// If the site is not connected, we can put it in a safe "house ad" mode.
if ( ( new Status() )->is_offline_mode() ) {
return array(
'approved' => true,
'active' => true,
'house' => true,
'unsafe' => false,
);
}
// Fetch the status from WPCOM endpoint.
$endpoint = sprintf( '/sites/%d/wordads/status', Jetpack::get_option( 'id' ) );
$response = Client::wpcom_json_api_request_as_blog( $endpoint );
$wordads_status_response = $response;
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'api_error', __( 'Error connecting to API.', 'jetpack' ), $response );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
return array(
'approved' => (bool) $body->approved,
'active' => (bool) $body->active,
'house' => (bool) $body->house,
'unsafe' => (bool) $body->unsafe,
);
}
/**
* Grab WordAds status from WP.com API and store as option
*
* @since 4.5.0
*/
public static function update_wordads_status_from_api() {
$status = self::get_wordads_status();
if ( ! is_wp_error( $status ) ) {
// Convert boolean options to string first to work around update_option not setting the option if the value is false.
// This sets the option to either '1' if true or '' if false.
update_option( 'wordads_approved', (string) $status['approved'], true );
update_option( 'wordads_active', (string) $status['active'], true );
update_option( 'wordads_house', (string) $status['house'], true );
update_option( 'wordads_unsafe', (string) $status['unsafe'], true );
}
}
/**
* Returns the ads.txt content needed to run WordAds.
*
* @return array string contents of the ads.txt file.
*
* @since 6.1.0
*/
public static function get_wordads_ads_txt() {
global $wordads_status_response;
$endpoint = sprintf( '/sites/%d/wordads/ads-txt', Jetpack::get_option( 'id' ) );
$response = Client::wpcom_json_api_request_as_blog( $endpoint );
$wordads_status_response = $response;
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'api_error', __( 'Error connecting to API.', 'jetpack' ), $response );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
$ads_txt = str_replace( '\\n', PHP_EOL, $body->adstxt );
return $ads_txt;
}
}
@@ -0,0 +1,67 @@
<?php
/**
* A utility class that provides functionality for manipulating arrays.
*
* @package automattic/jetpack
*/
/**
* WordAds_Array_Utils Class.
*/
final class WordAds_Array_Utils {
/**
* Converts a (potentially nested) array to a JavaScript object.
*
* Note: JS code strings should be prefixed with 'js:'.
*
* @param array $value The array to convert to a JavaScript object.
* @param bool $in_list True if we are processing an inner list (non-associative array).
*
* @return string String representation of the JavaScript object
*/
public static function array_to_js_object( array $value, bool $in_list = false ): string {
$properties = array();
foreach ( $value as $k => $v ) {
// Don't set property key for values from non-associative array.
$property_key = $in_list ? '' : "'$k': ";
if ( is_array( $v ) ) {
// Check for empty array.
if ( array() === $v ) {
$properties[] = "'$k': []";
continue;
}
// Check if this is a list and not an associative array.
if ( array_keys( $v ) === range( 0, count( $v ) - 1 ) ) {
// Apply recursively.
$properties[] = $property_key . '[ ' . self::array_to_js_object( $v, true ) . ' ]';
} else {
// Apply recursively.
$properties[] = $property_key . self::array_to_js_object( $v );
}
} elseif ( is_string( $v ) && strpos( $v, 'js:' ) === 0 ) {
// JS code. Strip the 'js:' prefix.
$properties[] = $property_key . substr( $v, 3 );
} elseif ( is_string( $v ) ) {
$properties[] = $property_key . "'" . addcslashes( $v, "'" ) . "'";
} elseif ( is_bool( $v ) ) {
$properties[] = $property_key . ( $v ? 'true' : 'false' );
} elseif ( $v === null ) {
$properties[] = $property_key . 'null';
} else {
$properties[] = $property_key . $v;
}
}
$output = implode( ', ', $properties );
if ( ! $in_list ) {
$output = '{ ' . $output . ' }';
}
return $output;
}
}
@@ -0,0 +1,283 @@
<?php
/**
* CCPA Class
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets;
/**
* Class WordAds_California_Privacy
*
* Implementation of [California Consumer Privacy Act] (https://leginfo.legislature.ca.gov/faces/codes_displayText.xhtml?lawCode=CIV&division=3.&title=1.81.5.&part=4.&chapter=&article=) as applicable to WordAds.
* Includes:
* - Do Not Sell or Share My Personal Information shortcode and widget.
* - Modal notice to toggle opt-in/opt-out.
* - Cookie handling. Implements IAB usprivacy cookie specifications.
* - Client side geo-detection of California visitors by IP address. Avoids issues with page caching.
*/
class WordAds_California_Privacy {
/**
* Initializes required scripts and shortcode.
*/
public static function init() {
// Initialize shortcode.
add_shortcode( 'ccpa-do-not-sell-link', array( __CLASS__, 'do_not_sell_link_shortcode' ) );
add_shortcode( 'privacy-do-not-sell-link', array( __CLASS__, 'do_not_sell_link_shortcode' ) );
}
/**
* Enqueue required CCPA JavaScript on the frontend.
*/
public static function enqueue_scripts() {
wp_enqueue_script(
'wordads_ccpa',
Assets::get_file_url_for_environment(
'_inc/build/wordads/js/wordads-ccpa.min.js',
'modules/wordads/js/wordads-ccpa.js'
),
array(),
JETPACK__VERSION,
true
);
wp_localize_script(
'wordads_ccpa',
'ccpaSettings',
array(
'defaultOptInCookieString' => esc_html( self::get_optin_cookie_string() ),
'defaultOptOutCookieString' => esc_html( self::get_optout_cookie_string() ),
'ccpaCssUrl' => esc_url( Assets::get_file_url_for_environment( '/css/wordads-ccpa.min.css', '/css/wordads-ccpa.css' ) . '?ver=' . JETPACK__VERSION ),
'ajaxUrl' => esc_url( admin_url( 'admin-ajax.php' ) ),
'ajaxNonce' => wp_create_nonce( 'ccpa_optout' ),
'forceApplies' => wp_json_encode( is_user_logged_in() && current_user_can( 'manage_options' ) ),
'strings' => array(
'pleaseWait' => esc_html__( 'Please Wait', 'jetpack' ),
),
)
);
}
/**
* Initializes handlers for admin AJAX.
*/
public static function init_ajax_actions() {
add_action( 'wp_ajax_privacy_optout', array( __CLASS__, 'handle_optout_request' ) );
add_action( 'wp_ajax_nopriv_privacy_optout', array( __CLASS__, 'handle_optout_request' ) );
add_action( 'wp_ajax_privacy_optout_markup', array( __CLASS__, 'handle_optout_markup' ) );
add_action( 'wp_ajax_nopriv_privacy_optout_markup', array( __CLASS__, 'handle_optout_markup' ) );
}
/**
* Outputs [privacy-do-not-sell-link] shortcode markup.
*
* @return string The generated shortcode markup.
*/
public static function do_not_sell_link_shortcode() {
// If in the customizer always display the link.
if ( is_customize_preview() ) {
return '<a href="#" class="ccpa-do-not-sell">' . self::get_optout_link_text() . '</a>';
}
// Load required scripts if the shortcode/widget is loaded on the page.
self::enqueue_scripts();
return '<a href="#" class="ccpa-do-not-sell" style="display: none;">' . self::get_optout_link_text() . '</a>';
}
/**
* Gets the text used to link to the opt-out page. By law must read 'Do Not Sell or Share My Personal Information'.
*
* @return mixed|string|void The text of the opt-out link.
*/
private static function get_optout_link_text() {
return __( 'Do Not Sell or Share My Personal Information', 'jetpack' );
}
/**
* Builds the value of the opt-out cookie.
* Format matches spec of [IAB U.S. Privacy String](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md).
*
* @param bool $optout True if setting an opt-out cookie.
*
* @return string The value to be stored in the opt-out cookie.
*/
private static function build_iab_privacy_string( $optout ) {
$values = array(
'1', // Specification version.
'Y', // Explicit notice to opt-out provided.
$optout ? 'Y' : 'N', // Opt-out of data sale.
'N', // Signatory to IAB Limited Service Provider Agreement.
);
return implode( $values );
}
/**
* Gets the name to be used for the opt-out cookie.
* Name matches spec of [IAB U.S. Privacy String](https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md).
*
* @return string The name of the opt-out cookie.
*/
private static function get_cookie_name() {
return 'usprivacy';
}
/**
* Gets the domain to be used for the opt-out cookie.
* Use the site's custom domain, or if the site has a wordpress.com subdomain, use .wordpress.com to share the cookie.
*
* @return string The domain to set for the opt-out cookie.
*/
public static function get_cookie_domain() {
$host = 'localhost';
if ( isset( $_SERVER['HTTP_HOST'] ) ) {
$host = filter_var( wp_unslash( $_SERVER['HTTP_HOST'] ) );
}
return '.wordpress.com' === substr( $host, -strlen( '.wordpress.com' ) ) ? '.wordpress.com' : '.' . $host;
}
/**
* Gets the value to be used when an opt-out cookie is set.
*
* @return string The value to store in the opt-out cookie.
*/
private static function get_optout_cookie_string() {
return self::build_iab_privacy_string( true );
}
/**
* Gets the value to be used when an opt-in cookie is set.
*
* @return string The value to store in the opt-in cookie.
*/
private static function get_optin_cookie_string() {
return self::build_iab_privacy_string( false );
}
/**
* Sets a cookie in the HTTP response to opt-out visitors from data sales.
*
* @return bool True if the cookie could be set.
*/
private static function set_optout_cookie() {
return setcookie( self::get_cookie_name(), self::get_optout_cookie_string(), time() + ( 5 * YEAR_IN_SECONDS ), '/', self::get_cookie_domain(), is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- Want this accessible.
}
/**
* Sets a cookie in the HTTP response to opt-in visitors from data sales.
*
* @return bool True if the cookie could be set.
*/
private static function set_optin_cookie() {
return setcookie( self::get_cookie_name(), self::get_optin_cookie_string(), time() + YEAR_IN_SECONDS, '/', self::get_cookie_domain(), is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- Want this accessible.
}
/**
* Handler for opt-in/opt-out AJAX request.
*/
public static function handle_optout_request() {
check_ajax_referer( 'ccpa_optout', 'security' );
$optout = isset( $_POST['optout'] ) && 'true' === $_POST['optout'];
$optout ? self::set_optout_cookie() : self::set_optin_cookie();
wp_send_json_success( $optout );
}
/**
* Handler for modal popup notice markup.
*/
public static function handle_optout_markup() {
check_ajax_referer( 'ccpa_optout', 'security' );
header( 'Content-Type: text/html; charset=utf-8' );
$policy_url = get_option( 'wordads_ccpa_privacy_policy_url' );
$default_disclosure = sprintf(
'<p>%s</p>
<p>%s</p>
<p><strong>%s</strong></p>
<p>%s</p>
<p>%s</p>
<p>%s</p>',
esc_html__( 'If you are a resident of certain US states, you have the right to opt out of the "sale" of your "personal information" under your state\'s privacy laws.', 'jetpack' ),
esc_html__( 'This site operates an ads program in partnership with third-party vendors who help place ads. Advertising cookies enable these ads partners to serve ads, to personalize those ads based on information like visits to this site and other sites on the internet, and to understand how users engage with those ads. Cookies collect certain information as part of the ads program, and we provide the following categories of information to third-party advertising partners: online identifiers and internet or other network or device activity (such as unique identifiers, cookie information, and IP address), and geolocation data (approximate location information from your IP address). This type of sharing with ads partners may be considered a "sale" of personal information under your state\'s privacy laws.', 'jetpack' ),
esc_html__( 'We never share information that identifies you personally, like your name or email address, as part of the advertising program.', 'jetpack' ),
esc_html__( 'If you\'d prefer not to see ads that are personalized based on information from your visits to this site, you can opt-out by toggling the "Do Not Sell or Share My Personal Information" switch below to the On position.', 'jetpack' ),
esc_html__( 'This opt-out is managed through cookies, so if you delete cookies, your browser is set to delete cookies automatically after a certain length of time, or if you visit this site with a different browser, you\'ll need to make this selection again.', 'jetpack' ),
esc_html__( 'After you opt-out you may still see ads, including personalized ones, on this site and other sites - they just won\'t be personalized based on information from your visits to this site.', 'jetpack' )
);
/**
* Filter on the default CCPA disclosure text.
*
* @see https://jetpack.com/support/ads/
*
* @module wordads
*
* @since 8.7.0
*
* @param string Default CCPA disclosure for WordAds.
*/
$disclosure = apply_filters( 'wordads_ccpa_disclosure', $default_disclosure );
?>
<div id="ccpa-modal" class="cleanslate">
<div class="components-modal__screen-overlay">
<div tabindex="0"></div>
<div role="dialog" aria-labelledby="dialog_label" aria-modal="true" class="components-modal__frame">
<div class="components-modal__content ccpa-settings">
<div class="components-modal__header">
<div class="components-modal__header-heading-container">
<h1 id="dialog_label" class="components-modal__header-heading"><?php esc_html_e( 'Do Not Sell or Share My Personal Information', 'jetpack' ); ?></h1>
</div>
<button type="button" aria-label="<?php esc_html_e( 'Close dialog', 'jetpack' ); ?>" class="components-button components-icon-button">
<svg aria-hidden="true" role="img" focusable="false" class="dashicon dashicons-no-alt" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"></path>
</svg>
</button>
</div>
<div class="ccpa-settings__intro-txt"><?php echo wp_kses( $disclosure, wp_kses_allowed_html( 'post' ) ); ?></div>
<div class="components-modal__footer">
<div role="form" class="ccpa-setting">
<label>
<span class="ccpa-setting__header"><?php esc_html_e( 'Do Not Sell or Share My Personal Information', 'jetpack' ); ?></span>
<span class="ccpa-setting__toggle components-form-toggle">
<input id="ccpa-opt-out" class="components-form-toggle__input opt-out" type="checkbox" value="false" autofocus />
<span class="components-form-toggle__track"></span>
<span class="components-form-toggle__thumb"></span>
<svg class="components-form-toggle__on" width="2" height="6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 6" role="img" aria-hidden="true" focusable="false"><path d="M0 0h2v6H0z"></path></svg>
<svg class="components-form-toggle__off" width="6" height="6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 6" role="img" aria-hidden="true" focusable="false"><path d="M3 1.5c.8 0 1.5.7 1.5 1.5S3.8 4.5 3 4.5 1.5 3.8 1.5 3 2.2 1.5 3 1.5M3 0C1.3 0 0 1.3 0 3s1.3 3 3 3 3-1.3 3-3-1.3-3-3-3z"></path></svg>
</span>
<span class="ccpa-setting__toggle-text ccpa-setting__toggle-text-off"><?php esc_html_e( 'Off', 'jetpack' ); ?></span>
<span class="ccpa-setting__toggle-text ccpa-setting__toggle-text-on"><?php esc_html_e( 'On', 'jetpack' ); ?></span>
</label>
</div>
<div class="components-modal__footer-bottom">
<button class="components-button is-button is-primary"><?php esc_html_e( 'Close', 'jetpack' ); ?></button>
<?php
if ( $policy_url ) {
printf(
'<a href="%s" class="ccpa-privacy">%s</a>',
esc_url( $policy_url ),
esc_html__( 'View Privacy Policy', 'jetpack' )
);
}
?>
</div>
</div>
</div>
</div>
</div>
</div>
<?php
wp_die();
}
}
@@ -0,0 +1,39 @@
<?php
/**
* CCPA Do Not Sell Widget
*
* @package automattic/jetpack
*/
/**
* Class WordAds_Ccpa_Do_Not_Sell_Link_Widget
*/
class WordAds_Ccpa_Do_Not_Sell_Link_Widget extends WP_Widget {
/**
* WordAds_Ccpa_Do_Not_Sell_Link_Widget constructor.
*/
public function __construct() {
parent::__construct(
'wordads_ccpa_do_not_sell_link_widget',
/** This filter is documented in modules/widgets/facebook-likebox.php */
apply_filters( 'jetpack_widget_name', __( 'Do Not Sell Link (US Privacy)', 'jetpack' ) ),
array(
'description' => __( 'Inserts "Do Not Sell or Share My Personal Information" link required by some US states to opt-out of targeted advertising', 'jetpack' ),
'customize_selective_refresh' => true,
)
);
}
/**
* Widget outputter.
*
* @param array $args Widget args.
* @param array $instance Widget instance.
*/
public function widget( $args, $instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo do_shortcode( '[privacy-do-not-sell-link]' );
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
@@ -0,0 +1,120 @@
<?php
/**
* WordAds Consent Management Provider
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets;
/**
* Class WordAds_Consent_Management_Provider
*
* This is an integration with the GDPR Consent Management Provider
* to comply with GDPR requirements for privacy and transparency related to advertising.
*/
class WordAds_Consent_Management_Provider {
/**
* IAB specified cookie name for storing the consent string.
*/
const COOKIE_NAME = 'euconsent-v2';
/**
* Initializes loading of the frontend framework.
*/
public static function init() {
// Prevent Cookies & Consent banner from displaying when the CMP is active.
add_filter( 'jetpack_disable_eu_cookie_law_widget', '__return_true' );
add_filter( 'jetpack_disable_cookie_consent_block', '__return_true' );
// Enqueue scripts.
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_frontend_scripts' ) );
}
/**
* AJAX handlers for fetching purposes and vendor data and setting the cookie serverside.
*
* Serverside cookie used so that the expiration can be longer than one week.
* This function is called from: /mu-plugins/wordads-ajax.php to ensure they run on all
* requests including admin requests.
*/
public static function init_ajax_actions() {
add_action( 'wp_ajax_gdpr_set_consent', array( __CLASS__, 'handle_set_consent_request' ) );
add_action( 'wp_ajax_nopriv_gdpr_set_consent', array( __CLASS__, 'handle_set_consent_request' ) );
}
/**
* Handler for setting consent cookie AJAX request.
*/
public static function handle_set_consent_request() {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( ! isset( $_POST['consent'] ) ) {
wp_send_json_error();
}
// TODO: Is there better sanitizing we can do here?
$consent = trim( wp_unslash( $_POST['consent'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
setcookie( self::COOKIE_NAME, $consent, time() + YEAR_IN_SECONDS, '/', self::get_cookie_domain(), is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- Client side CMP needs to be able to read this value.
wp_send_json_success( true );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Enqueues the main frontend Javascript.
*/
public static function enqueue_frontend_scripts() {
Assets::register_script(
'cmp_script_loader',
'_inc/build/wordads/js/cmp-loader.min.js',
JETPACK__PLUGIN_FILE,
array(
'nonmin_path' => 'modules/wordads/js/cmp-loader.js',
'dependencies' => array(),
'enqueue' => true,
'version' => JETPACK__VERSION,
)
);
wp_enqueue_script(
'cmp_config_script',
esc_url( self::get_config_url() ),
array( 'cmp_script_loader' ),
JETPACK__VERSION,
false
);
}
/**
* Gets the value to be used when an opt-in cookie is set.
*
* @return string The value to store in the opt-in cookie.
*/
private static function get_config_url() {
return sprintf(
'https://public-api.wordpress.com/wpcom/v2/sites/%1$d/cmp/configuration/%2$s/?_jsonp=a8c_cmp_callback',
(int) Jetpack_Options::get_option( 'id' ),
strtolower( get_locale() ) // Defaults to en_US not en.
);
}
/**
* Gets the domain to be used for the opt-out cookie.
* Use the site's custom domain, or if the site has a wordpress.com subdomain, use .wordpress.com to share the cookie.
*
* @return string The domain to set for the opt-out cookie.
*/
public static function get_cookie_domain() {
$host = 'localhost';
if ( isset( $_SERVER['HTTP_HOST'] ) ) {
$host = filter_var( wp_unslash( $_SERVER['HTTP_HOST'] ) );
}
return '.wordpress.com' === substr( $host, -strlen( '.wordpress.com' ) ) ? '.wordpress.com' : '.' . $host;
}
}
@@ -0,0 +1,53 @@
<?php
/**
* WordAds cron tasks.
*
* @package automattic/jetpack
*/
/**
* WordAds cron tasks
*
* @since 4.5.0
*/
class WordAds_Cron {
/**
* Add the actions the cron tasks will use
*
* @since 4.5.0
*/
public function __construct() {
add_action( 'wordads_cron_status', array( $this, 'update_wordads_status' ) );
}
/**
* Registered scheduled events on activation
*
* @since 4.5.0
*/
public static function activate() {
wp_schedule_event( time(), 'daily', 'wordads_cron_status' );
}
/**
* Clear scheduled hooks on deactivation
*
* @since 4.5.0
*/
public static function deactivate() {
wp_clear_scheduled_hook( 'wordads_cron_status' );
}
/**
* Grab WordAds status from WP.com API
*
* @since 4.5.0
*/
public static function update_wordads_status() {
WordAds_API::update_wordads_status_from_api();
}
}
global $wordads_cron;
$wordads_cron = new WordAds_Cron();
@@ -0,0 +1,325 @@
<?php
/**
* WordAds Param Class file.
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Status;
/**
* Class WordAds_Params
*
* Sets parameters for WordAds.
*/
class WordAds_Params {
/**
* WordAds options
*
* @var array
*/
public $options;
/**
* Current URL
*
* @access public
* @var string
*/
public $url;
/**
* Is this site served by CloudFlare?
*
* @access public
* @var bool
*/
public $cloudflare;
/**
* Jetpack Blog ID
*
* @var mixed
*/
public $blog_id;
/**
* Determine if the current User Agent is a mobile device
*
* @var bool
*/
public $mobile_device;
/**
* WordAds targeting tags
*
* @var array
*/
public $targeting_tags;
/**
* Type of page that is being loaded
*
* @var string
*/
public $page_type;
/**
* Page type code for IPW config
*
* @var int
*/
public $page_type_ipw;
/**
* Is this an AMP request?
*
* @var bool
*/
public $is_amp;
/**
* Setup parameters for serving the ads
*
* @since 4.5.0
*/
public function __construct() {
// WordAds setting => default.
$settings = array(
'wordads_approved' => false,
'wordads_active' => false,
'wordads_house' => true,
'wordads_unsafe' => false,
'enable_header_ad' => true,
'wordads_second_belowpost' => true,
'wordads_inline_enabled' => true,
'wordads_bottom_sticky_enabled' => false,
'wordads_sidebar_sticky_right_enabled' => false,
'wordads_display_front_page' => true,
'wordads_display_post' => true,
'wordads_display_page' => true,
'wordads_display_archive' => true,
'wordads_custom_adstxt' => '',
'wordads_custom_adstxt_enabled' => false,
'wordads_ccpa_enabled' => false,
'wordads_ccpa_privacy_policy_url' => get_option( 'wp_page_for_privacy_policy' ) ? get_permalink( (int) get_option( 'wp_page_for_privacy_policy' ) ) : '',
'wordads_cmp_enabled' => false,
);
// Grab settings, or set as default if it doesn't exist.
$this->options = array();
foreach ( $settings as $setting => $default ) {
$option = get_option( $setting, null );
if ( $option === null ) {
// Handle retroactively setting wordads_custom_adstxt_enabled to true if custom ads.txt content is already entered.
if ( 'wordads_custom_adstxt_enabled' === $setting ) {
$default = get_option( 'wordads_custom_adstxt' ) !== '';
}
// Convert boolean options to string first to work around update_option not setting the option if the value is false.
// This sets the option to either '1' if true or '' if false.
update_option( $setting, (string) $default, true );
$option = $default;
}
$this->options[ $setting ] = is_bool( $default ) ? (bool) $option : $option;
}
$this->url = esc_url_raw( ( is_ssl() ? 'https' : 'http' ) . '://' . ( isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : 'localhost' ) . ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' ) );
if ( str_contains( $this->url, '?' ) && ! isset( $_GET['p'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->url = substr( $this->url, 0, strpos( $this->url, '?' ) );
}
$this->cloudflare = self::is_cloudflare();
$this->blog_id = Jetpack::get_option( 'id', 0 );
$this->mobile_device = jetpack_is_mobile( 'any', true );
$this->targeting_tags = array(
'WordAds' => 1,
'BlogId' => ( new Status() )->is_offline_mode() ? 0 : Jetpack_Options::get_option( 'id' ),
'Domain' => esc_js( wp_parse_url( home_url(), PHP_URL_HOST ) ),
'PageURL' => esc_js( $this->url ),
'LangId' => str_contains( get_bloginfo( 'language' ), 'en' ) ? 1 : 0, // TODO something else?
'AdSafe' => 1, // TODO.
);
$this->is_amp = function_exists( 'amp_is_request' ) && amp_is_request();
}
/**
* Is this a mobile device?
*
* @return boolean true if the user is browsing on a mobile device (iPad not included)
*
* @since 4.5.0
*/
public function is_mobile() {
return ! empty( $this->mobile_device );
}
/**
* Is this site served by CloudFlare?
*
* @return boolean true if site is being served via CloudFlare
*
* @since 4.5.0
*/
public static function is_cloudflare() {
if (
defined( 'WORDADS_CLOUDFLARE' )
|| isset( $_SERVER['HTTP_CF_CONNECTING_IP'] )
|| isset( $_SERVER['HTTP_CF_IPCOUNTRY'] )
|| isset( $_SERVER['HTTP_CF_VISITOR'] )
) {
return true;
}
return false;
}
/**
* Is this an iOS device?
*
* @return boolean true if user is browsing in iOS device
*
* @since 4.5.0
*/
public function is_ios() {
return in_array( $this->get_device(), array( 'ipad', 'iphone', 'ipod' ), true );
}
/**
* Returns the user's device (see user-agent.php) or 'desktop'
*
* @return string user device
*
* @since 4.5.0
*/
public function get_device() {
global $agent_info;
if ( ! empty( $this->mobile_device ) ) {
return $this->mobile_device;
}
if ( $agent_info->is_ipad() ) {
return 'ipad';
}
return 'desktop';
}
/**
* Get page type.
*
* @return string The type of page that is being loaded
*
* @since 4.5.0
*/
public function get_page_type() {
if ( ! empty( $this->page_type ) ) {
return $this->page_type;
}
if ( self::is_static_home() ) {
$this->page_type = 'static_home';
} elseif ( is_home() ) {
$this->page_type = 'home';
} elseif ( is_page() ) {
$this->page_type = 'page';
} elseif ( is_single() ) {
$this->page_type = 'post';
} elseif ( is_search() ) {
$this->page_type = 'search';
} elseif ( is_category() ) {
$this->page_type = 'category';
} elseif ( is_archive() ) {
$this->page_type = 'archive';
} else {
$this->page_type = 'wtf';
}
return $this->page_type;
}
/**
* Get IPW code.
*
* @return int The page type code for ipw config
*
* @since 5.6.0
*/
public function get_page_type_ipw() {
if ( ! empty( $this->page_type_ipw ) ) {
return $this->page_type_ipw;
}
$page_type_ipw = 6;
if ( self::is_static_home() || is_home() || is_front_page() ) {
$page_type_ipw = 0;
} elseif ( is_page() ) {
$page_type_ipw = 2;
} elseif ( is_singular() ) {
$page_type_ipw = 1;
} elseif ( is_search() ) {
$page_type_ipw = 4;
} elseif ( is_category() || is_tag() || is_archive() || is_author() ) {
$page_type_ipw = 3;
} elseif ( is_404() ) {
$page_type_ipw = 5;
}
$this->page_type_ipw = $page_type_ipw;
return $page_type_ipw;
}
/**
* Returns true if page is static home
*
* @return boolean true if page is static home
*
* @since 4.5.0
*/
public static function is_static_home() {
return is_front_page() &&
'page' === get_option( 'show_on_front' ) &&
get_option( 'page_on_front' );
}
/**
* Logic for if we should show an ad
*
* @since 4.5.0
*/
public function should_show() {
global $wp_query;
if ( ( is_front_page() || is_home() ) && ! $this->options['wordads_display_front_page'] ) {
return false;
}
if ( is_single() && ! $this->options['wordads_display_post'] ) {
return false;
}
if ( is_page() && ! $this->options['wordads_display_page'] ) {
return false;
}
if ( ( is_archive() || is_search() ) && ! $this->options['wordads_display_archive'] ) {
return false;
}
if ( is_single() || ( is_page() && ! is_home() ) ) {
return true;
}
// TODO this would be a good place for allowing the user to specify.
if ( ( is_home() || is_archive() || is_search() ) && 0 === $wp_query->current_post ) {
return true;
}
return false;
}
}
@@ -0,0 +1,48 @@
<?php
/**
* Wordads shortcode.
*
* Examples:
* [wordads]
*
* @package automattic/jetpack
*/
/**
* Class WordAds_Shortcode
*
* Handles the [wordads] shortcode.
*/
class WordAds_Shortcode {
/**
* Register our shortcode and enqueue necessary files.
*/
public static function init() {
global $wordads;
if ( empty( $wordads ) ) {
return null;
}
add_shortcode( 'wordads', array( self::class, 'handle_wordads_shortcode' ) );
}
/**
* Our [wordads] shortcode.
* Prints a WordAds Ad.
*
* @return string HTML for WordAds shortcode.
*/
public static function handle_wordads_shortcode() {
global $wordads;
if ( empty( $wordads ) ) {
return '<div>' . __( 'The WordAds module is not active', 'jetpack' ) . '</div>';
}
$html = '<div class="jetpack-wordad" itemscope itemtype="https://schema.org/WPAdBlock"></div>';
return $wordads->insert_inline_ad( $html );
}
}
@@ -0,0 +1,221 @@
<?php
/**
* Widget for adding ads to a sidebar.
*
* @package automattic/jetpack
*/
/**
* Widget for inserting an ad into your sidebar
*
* @since 4.5.0
*/
class WordAds_Sidebar_Widget extends WP_Widget {
/**
* Allowed tags.
*
* @var string[]
*/
private static $allowed_tags = array( 'mrec', 'wideskyscraper', 'leaderboard' );
/**
* Mapping array of widget sizes with the WordAds_Smart formats.
*
* @var string[]
*/
private static $sizes_x_smart_format = array(
'300x250' => 'sidebar_widget_mediumrectangle',
'728x90' => 'sidebar_widget_leaderboard',
'160x600' => 'sidebar_widget_wideskyscraper',
);
/**
* Number of widgets.
*
* @var int
*/
private static $num_widgets = 0;
/**
* WordAds_Sidebar_Widget constructor.
*/
public function __construct() {
parent::__construct(
'wordads_sidebar_widget',
/** This filter is documented in modules/widgets/facebook-likebox.php */
apply_filters( 'jetpack_widget_name', 'Ads' ),
array(
'description' => __( 'Insert an ad unit wherever you can place a widget.', 'jetpack' ),
'customize_selective_refresh' => true,
)
);
add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_widget_in_block_editor' ) );
}
/**
* Remove the Ad widget from the Legacy Widget block
*
* @param array $widget_types List of widgets that are currently removed from the Legacy Widget block.
*
* @return array $widget_types New list of widgets that will be removed.
*/
public function hide_widget_in_block_editor( $widget_types ) {
$widget_types[] = 'wordads_sidebar_widget';
return $widget_types;
}
/**
* The Widget outputter.
*
* @param array $args Widget args.
* @param array $instance The Widget instance.
*
* @return bool|void
*/
public function widget( $args, $instance ) {
global $wordads;
if ( $wordads->should_bail() ) {
return false;
}
if ( ! isset( $instance['unit'] ) ) {
$instance['unit'] = 'mrec';
}
++self::$num_widgets;
$width = WordAds::$ad_tag_ids[ $instance['unit'] ]['width'];
$height = WordAds::$ad_tag_ids[ $instance['unit'] ]['height'];
$unit_id = 1 === self::$num_widgets ? 3 : self::$num_widgets + 3; // 2nd belowpost is '4'
$section_id = 0 === $wordads->params->blog_id ?
WORDADS_API_TEST_ID :
$wordads->params->blog_id . $unit_id;
$smart_format = self::$sizes_x_smart_format[ "{$width}x{$height}" ];
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$is_watl_enabled = isset( $_GET['wordads-logging'] ) && isset( $_GET[ $smart_format ] ) && 'true' === $_GET[ $smart_format ];
// Get the widget snippet.
$widget_snippet = $this->get_widget_snippet( $instance, $section_id, $height, $width );
// Render the IPW or house ad if WATL is disabled.
if ( ! $is_watl_enabled ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $widget_snippet;
return;
}
// Remove linebreaks and sanitize.
$tag = esc_js( str_replace( array( "\n", "\t", "\r" ), '', $widget_snippet ) );
// Add the fallback to be processed by WATL.
$fallback_snippet = <<<HTML
<script type="text/javascript">
var sas_fallback = sas_fallback || [];
sas_fallback.push(
{ tag: "$tag", type: '$smart_format' }
);
</script>
HTML;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $fallback_snippet . $wordads::get_watl_ad_html_tag( $smart_format );
}
/**
* The widget snippet.
*
* @param array $instance The widget instance.
* @param string $section_id The section id.
* @param int $height The ad height.
* @param int $width The ad width.
*
* @return string
*/
private function get_widget_snippet( $instance, $section_id, $height, $width ) {
global $wordads;
if ( $wordads->option( 'wordads_house', true ) ) {
$unit = 'mrec';
if ( 'leaderboard' === $instance['unit'] && ! $this->params->mobile_device ) {
$unit = 'leaderboard';
} elseif ( 'wideskyscraper' === $instance['unit'] ) {
$unit = 'widesky';
}
$snippet = $wordads->get_house_ad( $unit );
} else {
return $wordads->get_ad_snippet( $section_id, $height, $width, 'widget' );
}
$about = __( 'Advertisements', 'jetpack' );
$unit = esc_attr( $instance['unit'] );
return <<<HTML
<div class="wpcnt">
<div class="wpa">
<span class="wpa-about">$about</span>
<div class="u $unit">
$snippet
</div>
</div>
</div>
HTML;
}
/**
* The widget settings form.
*
* @param array $instance Widget instance.
*
* @return string|void
*/
public function form( $instance ) {
// ad unit type.
if ( isset( $instance['unit'] ) ) {
$unit = $instance['unit'];
} else {
$unit = 'mrec';
}
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'unit' ) ); ?>"><?php esc_html_e( 'Tag Dimensions:', 'jetpack' ); ?></label>
<select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'unit' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'unit' ) ); ?>">
<?php
foreach ( WordAds::$ad_tag_ids as $ad_unit => $properties ) {
if ( ! in_array( $ad_unit, self::$allowed_tags, true ) ) {
continue;
}
$splits = explode( '_', $properties['tag'] );
$unit_pretty = "{$splits[0]} {$splits[1]}";
$selected = selected( $ad_unit, $unit, false );
echo "<option value='", esc_attr( $ad_unit ), "' ", esc_attr( $selected ), '>', esc_html( $unit_pretty ), '</option>';
}
?>
</select>
</p>
<?php
}
/**
* The Widget updater.
*
* @param array $new_instance The revised instance.
* @param array $old_instance Original instance.
*
* @return array
*/
public function update( $new_instance, $old_instance ) {
$instance = $old_instance;
if ( in_array( $new_instance['unit'], self::$allowed_tags, true ) ) {
$instance['unit'] = $new_instance['unit'];
} else {
$instance['unit'] = 'mrec';
}
return $instance;
}
}
@@ -0,0 +1,344 @@
<?php
/**
* An implementation for ads served through Equativ Smart Ad Server.
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets;
// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
require_once WORDADS_ROOT . '/php/class-wordads-array-utils.php';
/**
* Contains all the implementation details for Smart ads
*/
class WordAds_Smart {
/**
* The single instance of the class.
*
* @var WordAds_Smart
*/
protected static $instance = null;
/**
* The parameters for WordAds.
*
* @var WordAds_Params
*/
private $params;
/**
* Has Smart asset been enqueued?
*
* @var bool True if Smart asset has been enqueued.
*/
private $is_asset_enqueued = false;
/**
* Supported formats.
* sidebar_widget formats represents the legacy Jetpack sidebar widget.
*
* @var array
*/
private $formats = array(
'top' => array(
'enabled' => false,
),
'inline' => array(
'enabled' => false,
),
'belowpost' => array(
'enabled' => false,
),
'bottom_sticky' => array(
'enabled' => false,
),
'sidebar_sticky_right' => array(
'enabled' => false,
),
'gutenberg_rectangle' => array(
'enabled' => false,
),
'gutenberg_leaderboard' => array(
'enabled' => false,
),
'gutenberg_mobile_leaderboard' => array(
'enabled' => false,
),
'gutenberg_skyscraper' => array(
'enabled' => false,
),
'sidebar_widget_mediumrectangle' => array(
'enabled' => false,
),
'sidebar_widget_leaderboard' => array(
'enabled' => false,
),
'sidebar_widget_wideskyscraper' => array(
'enabled' => false,
),
'shortcode' => array(
'enabled' => false,
),
);
/**
* Private constructor.
*/
private function __construct() {
}
/**
* Main Class Instance.
*
* Ensures only one instance of WordAds_Smart is loaded or can be loaded.
*
* @return WordAds_Smart
*/
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Initialize the ads.
*
* @param WordAds_Params $params Object containing WordAds settings.
*
* @return void
*/
public function init( WordAds_Params $params ) {
$this->params = $params;
$this->enable_formats();
$this->override_formats_from_query_string();
if ( $this->has_any_format_enabled() ) {
$this->insert_ads();
}
}
/**
* Enqueue any front-end CSS and JS.
*
* @return void
*/
public function enqueue_assets() {
if ( $this->is_asset_enqueued ) {
return;
}
add_action( 'wp_head', array( $this, 'insert_config' ) );
Assets::register_script(
'adflow_script_loader',
'_inc/build/wordads/js/adflow-loader.min.js',
JETPACK__PLUGIN_FILE,
array(
'nonmin_path' => 'modules/wordads/js/adflow-loader.js',
'dependencies' => array(),
'enqueue' => true,
'version' => JETPACK__VERSION,
)
);
wp_enqueue_script(
'adflow_config',
esc_url( $this->get_config_url() ),
array( 'adflow_script_loader' ),
JETPACK__VERSION,
false
);
$this->is_asset_enqueued = true;
}
/**
* Inserts ad tags on the page.
*
* @return void
*/
private function insert_ads() {
if ( $this->params->is_amp ) {
return;
}
// Don't run on not found pages.
if ( is_404() ) {
return;
}
// Add the resource hints.
add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 10, 2 );
// Enqueue JS assets.
$this->enqueue_assets();
$is_static_front_page = is_front_page() && 'page' === get_option( 'show_on_front' );
if ( ! ( $is_static_front_page || is_home() ) ) {
if ( $this->formats['inline']['enabled'] ) {
add_filter(
'the_content',
array( $this, 'insert_inline_marker' ),
10
);
}
}
if ( $this->formats['bottom_sticky']['enabled'] ) {
// Disable IPW slot.
add_filter( 'wordads_iponweb_bottom_sticky_ad_disable', '__return_true', 10 );
}
if ( $this->formats['sidebar_sticky_right']['enabled'] ) {
// Disable IPW slot.
add_filter( 'wordads_iponweb_sidebar_sticky_right_ad_disable', '__return_true', 10 );
}
}
/**
* Inserts JS configuration used by watl.js.
*
* @return void
*/
public function insert_config() {
global $post;
$config = array(
'post_id' => ( $post instanceof WP_Post ) && is_singular( 'post' ) ? $post->ID : null,
'origin' => 'jetpack',
'theme' => get_stylesheet(),
'target' => $this->target_keywords(),
) + $this->formats;
// Do conversion.
$js_config = WordAds_Array_Utils::array_to_js_object( $config );
// Output script.
wp_print_inline_script_tag( "var wa_smart = $js_config; wa_smart.cmd = [];" );
}
/**
* Add the Smart resource hints.
*
* @param array $hints Domains for hinting.
* @param string $relation_type Resource type.
*
* @return array Domains for hinting.
*/
public function resource_hints( $hints, $relation_type ) {
if ( 'dns-prefetch' === $relation_type ) {
$hints[] = '//af.pubmine.com';
}
return $hints;
}
/**
* Gets the URL to a JSONP endpoint with configuration data.
*
* @return string The URL.
*/
private function get_config_url(): string {
return sprintf(
'https://public-api.wordpress.com/wpcom/v2/sites/%1$d/adflow/conf/?_jsonp=a8c_adflow_callback',
$this->params->blog_id
);
}
/**
* Places marker at the end of the content so inline can identify the post content container.
*
* @param string|null $content The post content.
* @return string|null The post content with the marker appended.
*/
public function insert_inline_marker( ?string $content ): ?string {
if ( null === $content ) {
return null;
}
$inline_ad_marker = '<span id="wordads-inline-marker" style="display: none;"></span>';
// Append the ad to the post content.
return $content . $inline_ad_marker;
}
/**
* Gets a formatted list of target keywords.
*
* @return string Formatted list of target keywords.
*/
private function target_keywords(): string {
$target_keywords = array_merge(
$this->get_blog_keywords(),
$this->get_language_keywords()
);
return implode( ';', $target_keywords );
}
/**
* Gets a formatted list of blog keywords.
*
* @return array The list of blog keywords.
*/
private function get_blog_keywords(): array {
return array( 'wp_blog_id=' . $this->params->blog_id );
}
/**
* Gets the site language formatted as a keyword.
*
* @return array The language as a keyword.
*/
private function get_language_keywords(): array {
return array( 'language=' . explode( '-', get_locale() )[0] );
}
/**
* Enable formats by post types and the display options.
*
* @return void
*/
private function enable_formats(): void {
$this->formats['top']['enabled'] = $this->params->options['enable_header_ad'];
$this->formats['inline']['enabled'] = is_singular( 'post' ) && $this->params->options['wordads_inline_enabled'];
$this->formats['belowpost']['enabled'] = $this->params->should_show();
$this->formats['bottom_sticky']['enabled'] = $this->params->options['wordads_bottom_sticky_enabled'];
$this->formats['sidebar_sticky_right']['enabled'] = $this->params->options['wordads_sidebar_sticky_right_enabled'];
}
/**
* Allow format enabled override from query string, eg. ?inline=true.
*
* @return void
*/
private function override_formats_from_query_string(): void {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['wordads-logging'] ) ) {
return;
}
foreach ( $this->formats as $format_type => $_ ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET[ $format_type ] ) && 'true' === $_GET[ $format_type ] ) {
$this->formats[ $format_type ]['enabled'] = true;
}
}
}
/**
* Check if has any format enabled.
*
* @return bool True if enabled, false otherwise.
*/
private function has_any_format_enabled(): bool {
return in_array( true, array_column( $this->formats, 'enabled' ), true );
}
}
@@ -0,0 +1,8 @@
<?php
/**
* Amazon Network.
*
* @package automattic/jetpack
*/
// stub.