Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
WordAds_Smart
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 15
1260
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 instance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 init
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 enqueue_assets
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 insert_ads
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
90
 insert_config
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 resource_hints
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_config_url
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 insert_inline_marker
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 target_keywords
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_blog_keywords
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_language_keywords
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enable_formats
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 override_formats_from_query_string
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 has_any_format_enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 *  An implementation for ads served through Equativ Smart Ad Server.
4 *
5 * @package automattic/jetpack
6 */
7
8use Automattic\Jetpack\Assets;
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 );
12}
13
14require_once WORDADS_ROOT . '/php/class-wordads-array-utils.php';
15
16/**
17 * Contains all the implementation details for Smart ads
18 */
19class WordAds_Smart {
20
21    /**
22     * The single instance of the class.
23     *
24     * @var WordAds_Smart
25     */
26    protected static $instance = null;
27
28    /**
29     * The parameters for WordAds.
30     *
31     * @var WordAds_Params
32     */
33    private $params;
34
35    /**
36     * Has Smart asset been enqueued?
37     *
38     * @var bool True if Smart asset has been enqueued.
39     */
40    private $is_asset_enqueued = false;
41
42    /**
43     * Supported formats.
44     * sidebar_widget formats represents the legacy Jetpack sidebar widget.
45     *
46     * @var array
47     */
48    private $formats = array(
49        'top'                            => array(
50            'enabled' => false,
51        ),
52        'inline'                         => array(
53            'enabled' => false,
54        ),
55        'belowpost'                      => array(
56            'enabled' => false,
57        ),
58        'bottom_sticky'                  => array(
59            'enabled' => false,
60        ),
61        'sidebar_sticky_right'           => array(
62            'enabled' => false,
63        ),
64        'gutenberg_rectangle'            => array(
65            'enabled' => false,
66        ),
67        'gutenberg_leaderboard'          => array(
68            'enabled' => false,
69        ),
70        'gutenberg_mobile_leaderboard'   => array(
71            'enabled' => false,
72        ),
73        'gutenberg_skyscraper'           => array(
74            'enabled' => false,
75        ),
76        'sidebar_widget_mediumrectangle' => array(
77            'enabled' => false,
78        ),
79        'sidebar_widget_leaderboard'     => array(
80            'enabled' => false,
81        ),
82        'sidebar_widget_wideskyscraper'  => array(
83            'enabled' => false,
84        ),
85        'shortcode'                      => array(
86            'enabled' => false,
87        ),
88    );
89
90    /**
91     * Private constructor.
92     */
93    private function __construct() {
94    }
95
96    /**
97     * Main Class Instance.
98     *
99     * Ensures only one instance of WordAds_Smart is loaded or can be loaded.
100     *
101     * @return WordAds_Smart
102     */
103    public static function instance(): self {
104        if ( null === self::$instance ) {
105            self::$instance = new self();
106        }
107        return self::$instance;
108    }
109
110    /**
111     * Initialize the ads.
112     *
113     * @param WordAds_Params $params Object containing WordAds settings.
114     *
115     * @return void
116     */
117    public function init( WordAds_Params $params ) {
118        $this->params = $params;
119
120        $this->enable_formats();
121        $this->override_formats_from_query_string();
122
123        if ( $this->has_any_format_enabled() ) {
124            $this->insert_ads();
125        }
126    }
127
128    /**
129     * Enqueue any front-end CSS and JS.
130     *
131     * @return void
132     */
133    public function enqueue_assets() {
134
135        if ( $this->is_asset_enqueued ) {
136            return;
137        }
138
139        add_action( 'wp_head', array( $this, 'insert_config' ) );
140
141        Assets::register_script(
142            'adflow_script_loader',
143            '_inc/build/wordads/js/adflow-loader.min.js',
144            JETPACK__PLUGIN_FILE,
145            array(
146                'nonmin_path'  => 'modules/wordads/js/adflow-loader.js',
147                'dependencies' => array(),
148                'enqueue'      => true,
149                'version'      => JETPACK__VERSION,
150            )
151        );
152
153        wp_enqueue_script(
154            'adflow_config',
155            esc_url( $this->get_config_url() ),
156            array( 'adflow_script_loader' ),
157            JETPACK__VERSION,
158            false
159        );
160
161        $this->is_asset_enqueued = true;
162    }
163
164    /**
165     * Inserts ad tags on the page.
166     *
167     * @return void
168     */
169    private function insert_ads() {
170        if ( $this->params->is_amp ) {
171            return;
172        }
173
174        // Don't run on not found pages.
175        if ( is_404() ) {
176            return;
177        }
178
179        // Add the resource hints.
180        add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 10, 2 );
181
182        // Enqueue JS assets.
183        $this->enqueue_assets();
184
185        $is_static_front_page = is_front_page() && 'page' === get_option( 'show_on_front' );
186
187        if ( ! ( $is_static_front_page || is_home() ) ) {
188            if ( $this->formats['inline']['enabled'] ) {
189                add_filter(
190                    'the_content',
191                    array( $this, 'insert_inline_marker' ),
192                    10
193                );
194            }
195        }
196
197        if ( $this->formats['bottom_sticky']['enabled'] ) {
198            // Disable IPW slot.
199            add_filter( 'wordads_iponweb_bottom_sticky_ad_disable', '__return_true', 10 );
200        }
201
202        if ( $this->formats['sidebar_sticky_right']['enabled'] ) {
203            // Disable IPW slot.
204            add_filter( 'wordads_iponweb_sidebar_sticky_right_ad_disable', '__return_true', 10 );
205        }
206    }
207
208    /**
209     * Inserts JS configuration used by watl.js.
210     *
211     * @return void
212     */
213    public function insert_config() {
214        global $post;
215
216        $config = array(
217            'post_id' => ( $post instanceof WP_Post ) && is_singular( 'post' ) ? $post->ID : null,
218            'origin'  => 'jetpack',
219            'theme'   => get_stylesheet(),
220            'target'  => $this->target_keywords(),
221        ) + $this->formats;
222
223        // Do conversion.
224        $js_config = WordAds_Array_Utils::array_to_js_object( $config );
225
226        // Output script.
227        wp_print_inline_script_tag( "var wa_smart = $js_config; wa_smart.cmd = [];" );
228    }
229
230    /**
231     * Add the Smart resource hints.
232     *
233     * @param array  $hints Domains for hinting.
234     * @param string $relation_type Resource type.
235     *
236     * @return array Domains for hinting.
237     */
238    public function resource_hints( $hints, $relation_type ) {
239        if ( 'dns-prefetch' === $relation_type ) {
240            $hints[] = '//af.pubmine.com';
241        }
242
243        return $hints;
244    }
245
246    /**
247     * Gets the URL to a JSONP endpoint with configuration data.
248     *
249     * @return string The URL.
250     */
251    private function get_config_url(): string {
252        return sprintf(
253            'https://public-api.wordpress.com/wpcom/v2/sites/%1$d/adflow/conf/?_jsonp=a8c_adflow_callback',
254            $this->params->blog_id
255        );
256    }
257
258    /**
259     * Places marker at the end of the content so inline can identify the post content container.
260     *
261     * @param string|null $content The post content.
262     * @return string|null The post content with the marker appended.
263     */
264    public function insert_inline_marker( ?string $content ): ?string {
265        if ( null === $content ) {
266            return null;
267        }
268        $inline_ad_marker = '<span id="wordads-inline-marker" style="display: none;"></span>';
269
270        // Append the ad to the post content.
271        return $content . $inline_ad_marker;
272    }
273
274    /**
275     * Gets a formatted list of target keywords.
276     *
277     * @return string Formatted list of target keywords.
278     */
279    private function target_keywords(): string {
280        $target_keywords = array_merge(
281            $this->get_blog_keywords(),
282            $this->get_language_keywords()
283        );
284
285        return implode( ';', $target_keywords );
286    }
287
288    /**
289     * Gets a formatted list of blog keywords.
290     *
291     * @return array The list of blog keywords.
292     */
293    private function get_blog_keywords(): array {
294        return array( 'wp_blog_id=' . $this->params->blog_id );
295    }
296
297    /**
298     * Gets the site language formatted as a keyword.
299     *
300     * @return array The language as a keyword.
301     */
302    private function get_language_keywords(): array {
303        return array( 'language=' . explode( '-', get_locale() )[0] );
304    }
305
306    /**
307     * Enable formats by post types and the display options.
308     *
309     * @return void
310     */
311    private function enable_formats(): void {
312        $this->formats['top']['enabled']                  = $this->params->options['enable_header_ad'];
313        $this->formats['inline']['enabled']               = is_singular( 'post' ) && $this->params->options['wordads_inline_enabled'];
314        $this->formats['belowpost']['enabled']            = $this->params->should_show();
315        $this->formats['bottom_sticky']['enabled']        = $this->params->options['wordads_bottom_sticky_enabled'];
316        $this->formats['sidebar_sticky_right']['enabled'] = $this->params->options['wordads_sidebar_sticky_right_enabled'];
317    }
318
319    /**
320     * Allow format enabled override from query string, eg. ?inline=true.
321     *
322     * @return void
323     */
324    private function override_formats_from_query_string(): void {
325        // phpcs:disable WordPress.Security.NonceVerification.Recommended
326        if ( ! isset( $_GET['wordads-logging'] ) ) {
327            return;
328        }
329
330        foreach ( $this->formats as $format_type => $_ ) {
331            // phpcs:disable WordPress.Security.NonceVerification.Recommended
332            if ( isset( $_GET[ $format_type ] ) && 'true' === $_GET[ $format_type ] ) {
333                $this->formats[ $format_type ]['enabled'] = true;
334            }
335        }
336    }
337
338    /**
339     * Check if has any format enabled.
340     *
341     * @return bool True if enabled, false otherwise.
342     */
343    private function has_any_format_enabled(): bool {
344        return in_array( true, array_column( $this->formats, 'enabled' ), true );
345    }
346}