Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Data_Sync
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 9
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 get_instance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_action_nonces_for_entry
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 _print_options_script_tag
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 attach_to_plugin
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 get_registry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 register_readonly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register_action
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This is the main file for the Data_Sync package.
4 *
5 * It's responsible for setting up the registry and the endpoints.
6 *
7 * Setting up with something like this:
8 *
9 * ```
10 *      class Widget_Status extends Data_Sync_Handler {}
11 *      class Widget_Data extends Data_Sync_Handler {}
12 *
13 *      $instance = Data_Sync::setup( 'jetpack_boost', 'jetpack-boost' );
14 *      $instance->register( 'widget_status', new Widget_Status() );
15 *      $instance->register( 'widget_data', new Widget_Data() );
16 *
17 * ```
18 *
19 * This will to create two endpoints: `/wp-json/jetpack-boost/widget-status` and `/wp-json/jetpack-boost/widget-data`
20 * and pass the following variables to the `jetpack-boost` script handle.
21 *
22 * Note that keys for URLs are always automatically transformed to kebab-case, so `widget_status` becomes `widget-status`,
23 * and it's expected that keys are always in snake_case when referencing options.
24 * They're only transformed to kebab-case when used in URLs.
25 *
26 * ```
27 *    jetpack_boost = {
28 *        rest_api: {
29 *            value: 'https://example.com/wp-json/jetpack-boost',
30 *            nonce: '1234567890'
31 *        },
32 *        widget_status: {
33 *            value: 'active',
34 *            nonce: '1234567890'
35 *        },
36 *        widget_data: {
37 *            value: { ... },
38 *            nonce: '1234567890'
39 *        }
40 *    }
41 * ```
42 *
43 *
44 * To access the data from WordPress, you can ask the registry for the entry:*
45 * ```
46 *    $registry = Registry::get_instance( 'jetpack_boost' );
47 *    $entry = $registry->get( 'widget_status' );
48 *    $entry->get(); // 'active'
49 * ```
50 *
51 *
52 * To make it easier to access the data, you should probably create a dedicated helper function:
53 * ```
54 *    function jetpack_boost_get_data( $key ) {
55 *        $registry = Registry::get_instance( 'jetpack_boost' );
56 *        $entry = $registry->get( $key );
57 *        return $entry->get();
58 *    }
59 * ```
60 *
61 * @package automattic/jetpack-wp-js-data-sync
62 */
63
64namespace Automattic\Jetpack\WP_JS_Data_Sync;
65
66use Automattic\Jetpack\Schema\Parser;
67use Automattic\Jetpack\Schema\Schema_Context;
68use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Entry_Can_Get;
69use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Lazy_Entry;
70
71final class Data_Sync {
72
73    const PACKAGE_VERSION = '0.6.8';
74
75    /**
76     * @var Registry
77     */
78    private $registry;
79
80    /**
81     * @var string Script Handle name to pass the variables to.
82     */
83    private $script_handle;
84
85    /**
86     * The Registry class is a singleton.
87     *
88     * @var Data_Sync[]
89     */
90    private static $instance = array();
91    /**
92     * @var string The namespace to use for the registry.
93     */
94    private $namespace;
95
96    public function __construct( $namespace ) {
97        $this->namespace = $namespace;
98        $this->registry  = new Registry( $namespace );
99    }
100
101    public static function get_instance( $namespace ) {
102        if ( ! isset( self::$instance[ $namespace ] ) ) {
103            self::$instance[ $namespace ] = new self( $namespace );
104        }
105
106        return self::$instance[ $namespace ];
107    }
108
109    /**
110     * Retrieve nonces for all action endpoints associated with a given entry.
111     *
112     * @param string $entry_key The key for the entry.
113     *
114     * @return array An associative array of action nonces.
115     */
116    private function get_action_nonces_for_entry( $entry_key ) {
117        // Assuming a method in Registry class to retrieve all action names for an entry
118        $action_names = $this->registry->get_action_names_for_entry( $entry_key );
119        $nonces       = array();
120
121        foreach ( $action_names as $action_name ) {
122            $nonce = $this->registry->get_action_nonce( $entry_key, $action_name );
123            if ( $nonce ) {
124                $nonces[ $action_name ] = $nonce;
125            }
126        }
127
128        return $nonces;
129    }
130
131    /**
132     * Don't call this method directly.
133     * It's only public so that it can be called as a hook
134     *
135     * @return void
136     */
137    // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
138    public function _print_options_script_tag() {
139        $data = array(
140            'rest_api' => array(
141                'value' => rest_url( $this->registry->get_namespace_http() ),
142                'nonce' => wp_create_nonce( 'wp_rest' ),
143            ),
144        );
145        foreach ( $this->registry->all() as $key => $entry ) {
146
147            $data[ $key ] = array(
148                'value' => $entry->is( Lazy_Entry::class ) ? null : $entry->get(),
149                'nonce' => $this->registry->get_endpoint( $key )->create_nonce(),
150            );
151
152            if ( DS_Utils::is_debug() ) {
153                $data[ $key ]['log'] = $entry->get_parser()->get_log();
154            }
155
156            if ( DS_Utils::debug_disable( $key ) ) {
157                unset( $data[ $key ]['value'] );
158            }
159
160            if ( $entry->is( Lazy_Entry::class ) ) {
161                $data[ $key ]['lazy'] = true;
162            }
163
164            // Include nonces for action endpoints associated with this entry
165            $action_nonces = $this->get_action_nonces_for_entry( $key );
166            if ( ! empty( $action_nonces ) ) {
167                $data[ $key ]['actions'] = $action_nonces;
168            }
169        }
170
171        wp_localize_script( $this->script_handle, $this->namespace, $data );
172    }
173
174    public function attach_to_plugin( $script_handle, $plugin_page_hook ) {
175        $this->script_handle = $script_handle;
176        add_action( $plugin_page_hook, array( $this, '_print_options_script_tag' ) );
177    }
178
179    public function get_registry() {
180        return $this->registry;
181    }
182
183    /**
184     * DataSync entries have to be registered before they can be used.
185     *
186     * Typically, entries are stored in WP Options, so this method
187     * is will default to registering entries as Data_Sync_Option.
188     *
189     * However, you can provide an `$entry` instance that subscribes Entry_Can_* methods.
190     * If you do, `Entry_Can_Get` interface is required, and all other Entry_Can_* interfaces are optional.
191     *
192     * @param string        $key    - The key to register the entry under.
193     * @param Parser        $parser - The parser to use for the entry.
194     * @param Entry_Can_Get $custom_entry_instance - The entry to register. If null, a new Data_Sync_Option will be created.
195     *
196     * @return void
197     */
198    public function register( $key, $parser, $custom_entry_instance = null ) {
199        $option_key = $this->namespace . '_' . $key;
200
201        // If a custom entry instance is provided, and it implements Entry_Can_Get, use that.
202        // Otherwise, this Entry will store data using Data_Sync_Option (wp_options).
203        $entry = ( $custom_entry_instance instanceof Entry_Can_Get )
204            ? $custom_entry_instance
205            : new Data_Sync_Option( $option_key );
206
207        /*
208         * ## Adapter
209         * This `register` method is inteded to be a shorthand for the most common use case.
210         *
211         * Custom entries can implement various interfaces depending on whether they can set, merge, delete, etc.
212         * However, the Registry expects an object that implements Data_Sync_Entry.
213         * That's why we wrap the Entry in an Adapter - giving it a guaranteed interface.
214         *
215         * ## Customization
216         * Entries can be flexible because they're wrapped in an Adapter.
217         * But you can also create a class that implements `Data_Sync_Entry` directly if you need to.
218         * In that case, you'd need to use:
219         * ```php
220         *      $Data_Sync->get_registry()->register(...)` instead of `$Data_Sync->register(...)
221         * ```
222         */
223        if ( method_exists( $parser, 'set_context' ) ) {
224            // @phan-suppress-next-line PhanUndeclaredMethod -- Phan misses the method_exists(). See https://github.com/phan/phan/issues/1204.
225            $parser->set_context( new Schema_Context( $key ) );
226        }
227        $entry_adapter = new Data_Sync_Entry_Adapter( $entry, $parser );
228        $this->registry->register( $key, $entry_adapter );
229    }
230
231    /**
232     * Register a readonly entry.
233     *
234     * @param string   $key The key to register the entry under.
235     * @param Parser   $parser The parser to use for the entry.
236     * @param callable $callback The callback to use for the entry.
237     *
238     * @return void
239     */
240    public function register_readonly(
241        $key,
242        $parser,
243        $callback
244    ) {
245        $this->register( $key, $parser, new Data_Sync_Readonly( $callback ) );
246    }
247
248    public function register_action(
249        $key,
250        $action_name,
251        $request_schema,
252        $instance
253    ) {
254        $this->registry->register_action( $key, $action_name, $request_schema, $instance );
255    }
256}