Completed
Push — fix/markdown-parser-breaking-s... ( 6ae5e6...f00a12 )
by Jeremy
222:51 queued 212:00
created

Options::init_before_send()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Options sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Defaults;
11
12
/**
13
 * Class to handle sync for options.
14
 */
15
class Options extends Module {
16
	/**
17
	 * Whitelist for options we want to sync.
18
	 *
19
	 * @access private
20
	 *
21
	 * @var array
22
	 */
23
	private $options_whitelist;
24
25
	/**
26
	 * Contentless options we want to sync.
27
	 *
28
	 * @access private
29
	 *
30
	 * @var array
31
	 */
32
	private $options_contentless;
33
34
	/**
35
	 * Sync module name.
36
	 *
37
	 * @access public
38
	 *
39
	 * @return string
40
	 */
41
	public function name() {
42
		return 'options';
43
	}
44
45
	/**
46
	 * Initialize options action listeners.
47
	 *
48
	 * @access public
49
	 *
50
	 * @param callable $callable Action handler callable.
51
	 */
52
	public function init_listeners( $callable ) {
53
		// Options.
54
		add_action( 'added_option', $callable, 10, 2 );
55
		add_action( 'updated_option', $callable, 10, 3 );
56
		add_action( 'deleted_option', $callable, 10, 1 );
57
58
		// Sync Core Icon: Detect changes in Core's Site Icon and make it syncable.
59
		add_action( 'add_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
60
		add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
61
		add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
62
63
		// Handle deprecated options.
64
		add_filter( 'jetpack_options_whitelist', array( $this, 'add_deprecated_options' ) );
65
66
		$whitelist_option_handler = array( $this, 'whitelist_options' );
67
		add_filter( 'jetpack_sync_before_enqueue_deleted_option', $whitelist_option_handler );
68
		add_filter( 'jetpack_sync_before_enqueue_added_option', $whitelist_option_handler );
69
		add_filter( 'jetpack_sync_before_enqueue_updated_option', $whitelist_option_handler );
70
	}
71
72
	/**
73
	 * Initialize options action listeners for full sync.
74
	 *
75
	 * @access public
76
	 *
77
	 * @param callable $callable Action handler callable.
78
	 */
79
	public function init_full_sync_listeners( $callable ) {
80
		add_action( 'jetpack_full_sync_options', $callable );
81
	}
82
83
	/**
84
	 * Initialize the module in the sender.
85
	 *
86
	 * @access public
87
	 */
88
	public function init_before_send() {
89
		// Full sync.
90
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_options', array( $this, 'expand_options' ) );
91
	}
92
93
	/**
94
	 * Set module defaults.
95
	 * Define the options whitelist and contentless options.
96
	 *
97
	 * @access public
98
	 */
99
	public function set_defaults() {
100
		$this->update_options_whitelist();
101
		$this->update_options_contentless();
102
	}
103
104
	/**
105
	 * Set module defaults at a later time.
106
	 *
107
	 * @access public
108
	 */
109
	public function set_late_default() {
110
		/** This filter is already documented in json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php */
111
		$late_options = apply_filters( 'jetpack_options_whitelist', array() );
112
		if ( ! empty( $late_options ) && is_array( $late_options ) ) {
113
			$this->options_whitelist = array_merge( $this->options_whitelist, $late_options );
114
		}
115
	}
116
117
	/**
118
	 * Add old deprecated options to the list of options to keep in sync.
119
	 *
120
	 * @since 8.8.0
121
	 *
122
	 * @access public
123
	 *
124
	 * @param array $options The default list of site options.
125
	 */
126
	public function add_deprecated_options( $options ) {
127
		global $wp_version;
128
129
		$deprecated_options = array(
130
			'blacklist_keys'    => '5.5-alpha', // Replaced by disallowed_keys.
131
			'comment_whitelist' => '5.5-alpha', // Replaced by comment_previously_approved.
132
		);
133
134
		foreach ( $deprecated_options as $option => $version ) {
135
			if ( version_compare( $wp_version, $version, '<=' ) ) {
136
				$options[] = $option;
137
			}
138
		}
139
140
		return $options;
141
	}
142
143
	/**
144
	 * Enqueue the options actions for full sync.
145
	 *
146
	 * @access public
147
	 *
148
	 * @param array   $config               Full sync configuration for this sync module.
149
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
150
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
151
	 * @return array Number of actions enqueued, and next module state.
152
	 */
153
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
154
		/**
155
		 * Tells the client to sync all options to the server
156
		 *
157
		 * @since 4.2.0
158
		 *
159
		 * @param boolean Whether to expand options (should always be true)
160
		 */
161
		do_action( 'jetpack_full_sync_options', true );
162
163
		// The number of actions enqueued, and next module state (true == done).
164
		return array( 1, true );
165
	}
166
167
	/**
168
	 * Send the options actions for full sync.
169
	 *
170
	 * @access public
171
	 *
172
	 * @param array $config Full sync configuration for this sync module.
173
	 * @param int   $send_until The timestamp until the current request can send.
174
	 * @param array $state This module Full Sync status.
175
	 *
176
	 * @return array This module Full Sync status.
177
	 */
178
	public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
179
		// we call this instead of do_action when sending immediately.
180
		$this->send_action( 'jetpack_full_sync_options', array( true ) );
181
182
		// The number of actions enqueued, and next module state (true == done).
183
		return array( 'finished' => true );
184
	}
185
186
	/**
187
	 * Retrieve an estimated number of actions that will be enqueued.
188
	 *
189
	 * @access public
190
	 *
191
	 * @param array $config Full sync configuration for this sync module.
192
	 * @return int Number of items yet to be enqueued.
193
	 */
194
	public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
195
		return 1;
196
	}
197
198
	/**
199
	 * Retrieve the actions that will be sent for this module during a full sync.
200
	 *
201
	 * @access public
202
	 *
203
	 * @return array Full sync actions of this module.
204
	 */
205
	public function get_full_sync_actions() {
206
		return array( 'jetpack_full_sync_options' );
207
	}
208
209
	/**
210
	 * Retrieve all options as per the current options whitelist.
211
	 * Public so that we don't have to store so much data all the options twice.
212
	 *
213
	 * @access public
214
	 *
215
	 * @return array All options.
216
	 */
217
	public function get_all_options() {
218
		$options       = array();
219
		$random_string = wp_generate_password();
220
		foreach ( $this->options_whitelist as $option ) {
221
			$option_value = get_option( $option, $random_string );
222
			if ( $option_value !== $random_string ) {
223
				$options[ $option ] = $option_value;
224
			}
225
		}
226
227
		// Add theme mods.
228
		$theme_mods_option = 'theme_mods_' . get_option( 'stylesheet' );
229
		$theme_mods_value  = get_option( $theme_mods_option, $random_string );
230
		if ( $theme_mods_value === $random_string ) {
231
			return $options;
232
		}
233
		$this->filter_theme_mods( $theme_mods_value );
234
		$options[ $theme_mods_option ] = $theme_mods_value;
235
		return $options;
236
	}
237
238
	/**
239
	 * Update the options whitelist to the default one.
240
	 *
241
	 * @access public
242
	 */
243
	public function update_options_whitelist() {
244
		$this->options_whitelist = Defaults::get_options_whitelist();
245
	}
246
247
	/**
248
	 * Set the options whitelist.
249
	 *
250
	 * @access public
251
	 *
252
	 * @param array $options The new options whitelist.
253
	 */
254
	public function set_options_whitelist( $options ) {
255
		$this->options_whitelist = $options;
256
	}
257
258
	/**
259
	 * Get the options whitelist.
260
	 *
261
	 * @access public
262
	 *
263
	 * @return array The options whitelist.
264
	 */
265
	public function get_options_whitelist() {
266
		return $this->options_whitelist;
267
	}
268
269
	/**
270
	 * Update the contentless options to the defaults.
271
	 *
272
	 * @access public
273
	 */
274
	public function update_options_contentless() {
275
		$this->options_contentless = Defaults::get_options_contentless();
276
	}
277
278
	/**
279
	 * Get the contentless options.
280
	 *
281
	 * @access public
282
	 *
283
	 * @return array Array of the contentless options.
284
	 */
285
	public function get_options_contentless() {
286
		return $this->options_contentless;
287
	}
288
289
	/**
290
	 * Reject any options that aren't whitelisted or contentless.
291
	 *
292
	 * @access public
293
	 *
294
	 * @param array $args The hook parameters.
295
	 * @return array $args The hook parameters.
296
	 */
297
	public function whitelist_options( $args ) {
298
		// Reject non-whitelisted options.
299
		if ( ! $this->is_whitelisted_option( $args[0] ) ) {
300
			return false;
301
		}
302
303
		// Filter our weird array( false ) value for theme_mods_*.
304
		if ( 'theme_mods_' === substr( $args[0], 0, 11 ) ) {
305
			$this->filter_theme_mods( $args[1] );
306
			if ( isset( $args[2] ) ) {
307
				$this->filter_theme_mods( $args[2] );
308
			}
309
		}
310
311
		// Set value(s) of contentless option to empty string(s).
312
		if ( $this->is_contentless_option( $args[0] ) ) {
313
			// Create a new array matching length of $args, containing empty strings.
314
			$empty    = array_fill( 0, count( $args ), '' );
315
			$empty[0] = $args[0];
316
			return $empty;
317
		}
318
319
		return $args;
320
	}
321
322
	/**
323
	 * Whether a certain option is whitelisted for sync.
324
	 *
325
	 * @access public
326
	 *
327
	 * @param string $option Option name.
328
	 * @return boolean Whether the option is whitelisted.
329
	 */
330
	public function is_whitelisted_option( $option ) {
331
		return in_array( $option, $this->options_whitelist, true ) || 'theme_mods_' === substr( $option, 0, 11 );
332
	}
333
334
	/**
335
	 * Whether a certain option is a contentless one.
336
	 *
337
	 * @access private
338
	 *
339
	 * @param string $option Option name.
340
	 * @return boolean Whether the option is contentless.
341
	 */
342
	private function is_contentless_option( $option ) {
343
		return in_array( $option, $this->options_contentless, true );
344
	}
345
346
	/**
347
	 * Filters out falsy values from theme mod options.
348
	 *
349
	 * @access private
350
	 *
351
	 * @param array $value Option value.
352
	 */
353
	private function filter_theme_mods( &$value ) {
354
		if ( is_array( $value ) && isset( $value[0] ) ) {
355
			unset( $value[0] );
356
		}
357
	}
358
359
	/**
360
	 * Handle changes in the core site icon and sync them.
361
	 *
362
	 * @access public
363
	 */
364
	public function jetpack_sync_core_icon() {
365
		$url = get_site_icon_url();
366
367
		$jetpack_url = \Jetpack_Options::get_option( 'site_icon_url' );
368
		if ( defined( 'JETPACK__PLUGIN_DIR' ) ) {
369
			if ( ! function_exists( 'jetpack_site_icon_url' ) ) {
370
				require_once JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php';
371
			}
372
			$jetpack_url = jetpack_site_icon_url();
373
		}
374
375
		// If there's a core icon, maybe update the option.  If not, fall back to Jetpack's.
376
		if ( ! empty( $url ) && $jetpack_url !== $url ) {
377
			// This is the option that is synced with dotcom.
378
			\Jetpack_Options::update_option( 'site_icon_url', $url );
379
		} elseif ( empty( $url ) ) {
380
			\Jetpack_Options::delete_option( 'site_icon_url' );
381
		}
382
	}
383
384
	/**
385
	 * Expand all options within a hook before they are serialized and sent to the server.
386
	 *
387
	 * @access public
388
	 *
389
	 * @param array $args The hook parameters.
390
	 * @return array $args The hook parameters.
391
	 */
392
	public function expand_options( $args ) {
393
		if ( $args[0] ) {
394
			return $this->get_all_options();
395
		}
396
397
		return $args;
398
	}
399
400
	/**
401
	 * Return Total number of objects.
402
	 *
403
	 * @param array $config Full Sync config.
404
	 *
405
	 * @return int total
406
	 */
407
	public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
408
		return count( Defaults::get_options_whitelist() );
409
	}
410
411
}
412