Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
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 / 75
0.00% covered (danger)
0.00%
0 / 7
756
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 / 32
0.00% covered (danger)
0.00%
0 / 1
56
 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 post editor screens.
107     */
108    public function enqueue_scripts() {
109        // Load in the post editor only — the `enqueue_block_editor_assets` action
110        // also fires in the site editor, widgets editor, and customizer.
111        $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
112        if ( ! $current_screen || 'post' !== $current_screen->base ) {
113            return;
114        }
115
116        if ( ! self::is_enabled() ) {
117            return;
118        }
119
120        $asset_file = self::get_asset_file();
121        if ( ! $asset_file ) {
122            return;
123        }
124
125        $version = $asset_file['version'] ?? '1.0.0';
126
127        wp_enqueue_script(
128            'content-research-gutenberg',
129            'https://widgets.wp.com/content-research/content-research-gutenberg.min.js',
130            array(),
131            $version,
132            true
133        );
134
135        wp_add_inline_script(
136            'content-research-gutenberg',
137            'window.contentResearchData = ' . wp_json_encode(
138                array(
139                    'enabled' => true,
140                ),
141                JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
142            ) . ';',
143            'before'
144        );
145
146        wp_enqueue_style(
147            'content-research-gutenberg-style',
148            'https://widgets.wp.com/content-research/content-research-gutenberg' . ( is_rtl() ? '.rtl.css' : '.css' ),
149            array(),
150            $version
151        );
152    }
153
154    /**
155     * Returns whether the current request is coming from the a8c proxy.
156     */
157    private static function is_proxied() {
158        return isset( $_SERVER['A8C_PROXIED_REQUEST'] )
159            ? sanitize_text_field( wp_unslash( $_SERVER['A8C_PROXIED_REQUEST'] ) )
160            : defined( 'A8C_PROXIED_REQUEST' ) && A8C_PROXIED_REQUEST;
161    }
162
163    /**
164     * Check if the feature is enabled.
165     *
166     * Restricted to automatticians proxied through a8c on WPCOM Simple sites.
167     *
168     * @return bool
169     */
170    public static function is_enabled() {
171        if ( ! ( new Host() )->is_wpcom_simple() ) {
172            return false;
173        }
174
175        if ( ! self::is_proxied() ) {
176            return false;
177        }
178
179        if ( ! function_exists( '\is_automattician' ) || ! \is_automattician() ) {
180            return false;
181        }
182
183        return true;
184    }
185}
186
187// Initialize the feature when this file is loaded.
188WPCOM_Content_Research::init();