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