Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_Content_Research
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 7
600
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 register_rest_api
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 get_asset_file
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
72
 enqueue_scripts
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
20
 is_proxied
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 is_enabled
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * WordPress.com Content Research feature.
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8namespace A8C\FSE;
9
10use Automattic\Jetpack\Status\Host;
11
12/**
13 * Class WPCOM_Content_Research
14 *
15 * Main feature class. Registers REST API endpoints and enqueues
16 * the frontend sidebar script on Gutenberg editor screens for
17 * automatticians proxied through a8c on WPCOM Simple sites.
18 */
19class WPCOM_Content_Research {
20
21    /**
22     * Class instance.
23     *
24     * @var WPCOM_Content_Research
25     */
26    private static $instance = null;
27
28    /**
29     * Initialize the feature.
30     */
31    public static function init() {
32        if ( self::$instance === null ) {
33            self::$instance = new self();
34        }
35    }
36
37    /**
38     * WPCOM_Content_Research constructor.
39     */
40    private function __construct() {
41        add_action( 'rest_api_init', array( $this, 'register_rest_api' ) );
42        add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_scripts' ) );
43    }
44
45    /**
46     * Register REST API endpoints.
47     */
48    public function register_rest_api() {
49        require_once __DIR__ . '/class-wp-rest-content-research-search.php';
50        ( new WP_REST_Content_Research_Search() )->register_rest_route();
51
52        require_once __DIR__ . '/class-wp-rest-content-research-summarize.php';
53        ( new WP_REST_Content_Research_Summarize() )->register_rest_route();
54    }
55
56    /**
57     * Get the asset file data (dependencies, version) from widgets.wp.com.
58     *
59     * Uses filesystem on Simple sites, HTTP on Atomic.
60     *
61     * @return array|null The asset file data or null on failure.
62     */
63    private static function get_asset_file() {
64        $filepath = 'widgets.wp.com/content-research/content-research-gutenberg.asset.json';
65
66        // Try filesystem first (Simple sites). Failures are not cached.
67        if ( file_exists( ABSPATH . $filepath ) ) {
68            $contents = file_get_contents( ABSPATH . $filepath );
69            if ( false !== $contents ) {
70                $data = json_decode( $contents, true );
71                if ( is_array( $data ) ) {
72                    return $data;
73                }
74            }
75        }
76
77        $cache_key = 'jetpack_mu_wpcom_cr_asset_' . md5( $filepath );
78        $cached    = get_transient( $cache_key );
79
80        if ( is_array( $cached ) ) {
81            return $cached;
82        }
83
84        // Fall back to HTTP (Atomic sites).
85        $request = wp_remote_get(
86            'https://' . $filepath,
87            array(
88                'timeout'     => 5,
89                'redirection' => 2,
90            )
91        );
92        if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
93            return null;
94        }
95
96        $data = json_decode( wp_remote_retrieve_body( $request ), true );
97        if ( is_array( $data ) ) {
98            set_transient( $cache_key, $data, 12 * HOUR_IN_SECONDS );
99            return $data;
100        }
101
102        return null;
103    }
104
105    /**
106     * Enqueue the Content Research sidebar script on editor screens.
107     */
108    public function enqueue_scripts() {
109        if ( ! self::is_enabled() ) {
110            return;
111        }
112
113        $asset_file = self::get_asset_file();
114        if ( ! $asset_file ) {
115            return;
116        }
117
118        $version = $asset_file['version'] ?? '1.0.0';
119
120        wp_enqueue_script(
121            'content-research-gutenberg',
122            'https://widgets.wp.com/content-research/content-research-gutenberg.min.js',
123            array(),
124            $version,
125            true
126        );
127
128        wp_add_inline_script(
129            'content-research-gutenberg',
130            'window.contentResearchData = ' . wp_json_encode(
131                array(
132                    'enabled' => true,
133                ),
134                JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
135            ) . ';',
136            'before'
137        );
138
139        wp_enqueue_style(
140            'content-research-gutenberg-style',
141            'https://widgets.wp.com/content-research/content-research-gutenberg' . ( is_rtl() ? '.rtl.css' : '.css' ),
142            array(),
143            $version
144        );
145    }
146
147    /**
148     * Returns whether the current request is coming from the a8c proxy.
149     */
150    private static function is_proxied() {
151        return isset( $_SERVER['A8C_PROXIED_REQUEST'] )
152            ? sanitize_text_field( wp_unslash( $_SERVER['A8C_PROXIED_REQUEST'] ) )
153            : defined( 'A8C_PROXIED_REQUEST' ) && A8C_PROXIED_REQUEST;
154    }
155
156    /**
157     * Check if the feature is enabled.
158     *
159     * Restricted to automatticians proxied through a8c on WPCOM Simple sites.
160     *
161     * @return bool
162     */
163    public static function is_enabled() {
164        if ( ! ( new Host() )->is_wpcom_simple() ) {
165            return false;
166        }
167
168        if ( ! self::is_proxied() ) {
169            return false;
170        }
171
172        if ( ! function_exists( '\is_automattician' ) || ! \is_automattician() ) {
173            return false;
174        }
175
176        return true;
177    }
178}
179
180// Initialize the feature when this file is loaded.
181WPCOM_Content_Research::init();