Completed
Push — branch-8.sync ( b15093 )
by Jeremy
07:09
created

Options::expand_options()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 7
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
		$whitelist_option_handler = array( $this, 'whitelist_options' );
64
		add_filter( 'jetpack_sync_before_enqueue_deleted_option', $whitelist_option_handler );
65
		add_filter( 'jetpack_sync_before_enqueue_added_option', $whitelist_option_handler );
66
		add_filter( 'jetpack_sync_before_enqueue_updated_option', $whitelist_option_handler );
67
	}
68
69
	/**
70
	 * Initialize options action listeners for full sync.
71
	 *
72
	 * @access public
73
	 *
74
	 * @param callable $callable Action handler callable.
75
	 */
76
	public function init_full_sync_listeners( $callable ) {
77
		add_action( 'jetpack_full_sync_options', $callable );
78
	}
79
80
	/**
81
	 * Initialize the module in the sender.
82
	 *
83
	 * @access public
84
	 */
85
	public function init_before_send() {
86
		// Full sync.
87
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_options', array( $this, 'expand_options' ) );
88
	}
89
90
	/**
91
	 * Set module defaults.
92
	 * Define the options whitelist and contentless options.
93
	 *
94
	 * @access public
95
	 */
96
	public function set_defaults() {
97
		$this->update_options_whitelist();
98
		$this->update_options_contentless();
99
	}
100
101
	/**
102
	 * Set module defaults at a later time.
103
	 *
104
	 * @access public
105
	 */
106
	public function set_late_default() {
107
		/** This filter is already documented in json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php */
108
		$late_options = apply_filters( 'jetpack_options_whitelist', array() );
109
		if ( ! empty( $late_options ) && is_array( $late_options ) ) {
110
			$this->options_whitelist = array_merge( $this->options_whitelist, $late_options );
111
		}
112
	}
113
114
	/**
115
	 * Enqueue the options actions for full sync.
116
	 *
117
	 * @access public
118
	 *
119
	 * @param array   $config               Full sync configuration for this sync module.
120
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
121
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
122
	 * @return array Number of actions enqueued, and next module state.
123
	 */
124
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
125
		/**
126
		 * Tells the client to sync all options to the server
127
		 *
128
		 * @since 4.2.0
129
		 *
130
		 * @param boolean Whether to expand options (should always be true)
131
		 */
132
		do_action( 'jetpack_full_sync_options', true );
133
134
		// The number of actions enqueued, and next module state (true == done).
135
		return array( 1, true );
136
	}
137
138
	/**
139
	 * Send the options actions for full sync.
140
	 *
141
	 * @access public
142
	 *
143
	 * @param array $config Full sync configuration for this sync module.
144
	 * @param int   $send_until The timestamp until the current request can send.
145
	 * @param array $state This module Full Sync status.
146
	 *
147
	 * @return array This module Full Sync status.
148
	 */
149
	public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
150
		// we call this instead of do_action when sending immediately.
151
		$this->send_action( 'jetpack_full_sync_options', array( true ) );
152
153
		// The number of actions enqueued, and next module state (true == done).
154
		return array( 'finished' => true );
155
	}
156
157
	/**
158
	 * Retrieve an estimated number of actions that will be enqueued.
159
	 *
160
	 * @access public
161
	 *
162
	 * @param array $config Full sync configuration for this sync module.
163
	 * @return int Number of items yet to be enqueued.
164
	 */
165
	public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
166
		return 1;
167
	}
168
169
	/**
170
	 * Retrieve the actions that will be sent for this module during a full sync.
171
	 *
172
	 * @access public
173
	 *
174
	 * @return array Full sync actions of this module.
175
	 */
176
	public function get_full_sync_actions() {
177
		return array( 'jetpack_full_sync_options' );
178
	}
179
180
	/**
181
	 * Retrieve all options as per the current options whitelist.
182
	 * Public so that we don't have to store so much data all the options twice.
183
	 *
184
	 * @access public
185
	 *
186
	 * @return array All options.
187
	 */
188
	public function get_all_options() {
189
		$options       = array();
190
		$random_string = wp_generate_password();
191
		foreach ( $this->options_whitelist as $option ) {
192
			$option_value = get_option( $option, $random_string );
193
			if ( $option_value !== $random_string ) {
194
				$options[ $option ] = $option_value;
195
			}
196
		}
197
198
		// Add theme mods.
199
		$theme_mods_option = 'theme_mods_' . get_option( 'stylesheet' );
200
		$theme_mods_value  = get_option( $theme_mods_option, $random_string );
201
		if ( $theme_mods_value === $random_string ) {
202
			return $options;
203
		}
204
		$this->filter_theme_mods( $theme_mods_value );
205
		$options[ $theme_mods_option ] = $theme_mods_value;
206
		return $options;
207
	}
208
209
	/**
210
	 * Update the options whitelist to the default one.
211
	 *
212
	 * @access public
213
	 */
214
	public function update_options_whitelist() {
215
		$this->options_whitelist = Defaults::get_options_whitelist();
216
	}
217
218
	/**
219
	 * Set the options whitelist.
220
	 *
221
	 * @access public
222
	 *
223
	 * @param array $options The new options whitelist.
224
	 */
225
	public function set_options_whitelist( $options ) {
226
		$this->options_whitelist = $options;
227
	}
228
229
	/**
230
	 * Get the options whitelist.
231
	 *
232
	 * @access public
233
	 *
234
	 * @return array The options whitelist.
235
	 */
236
	public function get_options_whitelist() {
237
		return $this->options_whitelist;
238
	}
239
240
	/**
241
	 * Update the contentless options to the defaults.
242
	 *
243
	 * @access public
244
	 */
245
	public function update_options_contentless() {
246
		$this->options_contentless = Defaults::get_options_contentless();
247
	}
248
249
	/**
250
	 * Get the contentless options.
251
	 *
252
	 * @access public
253
	 *
254
	 * @return array Array of the contentless options.
255
	 */
256
	public function get_options_contentless() {
257
		return $this->options_contentless;
258
	}
259
260
	/**
261
	 * Reject any options that aren't whitelisted or contentless.
262
	 *
263
	 * @access public
264
	 *
265
	 * @param array $args The hook parameters.
266
	 * @return array $args The hook parameters.
267
	 */
268
	public function whitelist_options( $args ) {
269
		// Reject non-whitelisted options.
270
		if ( ! $this->is_whitelisted_option( $args[0] ) ) {
271
			return false;
272
		}
273
274
		// Filter our weird array( false ) value for theme_mods_*.
275
		if ( 'theme_mods_' === substr( $args[0], 0, 11 ) ) {
276
			$this->filter_theme_mods( $args[1] );
277
			if ( isset( $args[2] ) ) {
278
				$this->filter_theme_mods( $args[2] );
279
			}
280
		}
281
282
		// Set value(s) of contentless option to empty string(s).
283
		if ( $this->is_contentless_option( $args[0] ) ) {
284
			// Create a new array matching length of $args, containing empty strings.
285
			$empty    = array_fill( 0, count( $args ), '' );
286
			$empty[0] = $args[0];
287
			return $empty;
288
		}
289
290
		return $args;
291
	}
292
293
	/**
294
	 * Whether a certain option is whitelisted for sync.
295
	 *
296
	 * @access public
297
	 *
298
	 * @param string $option Option name.
299
	 * @return boolean Whether the option is whitelisted.
300
	 */
301
	public function is_whitelisted_option( $option ) {
302
		return in_array( $option, $this->options_whitelist, true ) || 'theme_mods_' === substr( $option, 0, 11 );
303
	}
304
305
	/**
306
	 * Whether a certain option is a contentless one.
307
	 *
308
	 * @access private
309
	 *
310
	 * @param string $option Option name.
311
	 * @return boolean Whether the option is contentless.
312
	 */
313
	private function is_contentless_option( $option ) {
314
		return in_array( $option, $this->options_contentless, true );
315
	}
316
317
	/**
318
	 * Filters out falsy values from theme mod options.
319
	 *
320
	 * @access private
321
	 *
322
	 * @param array $value Option value.
323
	 */
324
	private function filter_theme_mods( &$value ) {
325
		if ( is_array( $value ) && isset( $value[0] ) ) {
326
			unset( $value[0] );
327
		}
328
	}
329
330
	/**
331
	 * Handle changes in the core site icon and sync them.
332
	 *
333
	 * @access public
334
	 */
335
	public function jetpack_sync_core_icon() {
336
		$url = get_site_icon_url();
337
338
		require_once JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php';
339
		// If there's a core icon, maybe update the option.  If not, fall back to Jetpack's.
340
		if ( ! empty( $url ) && jetpack_site_icon_url() !== $url ) {
341
			// This is the option that is synced with dotcom.
342
			\Jetpack_Options::update_option( 'site_icon_url', $url );
343
		} elseif ( empty( $url ) ) {
344
			\Jetpack_Options::delete_option( 'site_icon_url' );
345
		}
346
	}
347
348
	/**
349
	 * Expand all options within a hook before they are serialized and sent to the server.
350
	 *
351
	 * @access public
352
	 *
353
	 * @param array $args The hook parameters.
354
	 * @return array $args The hook parameters.
355
	 */
356
	public function expand_options( $args ) {
357
		if ( $args[0] ) {
358
			return $this->get_all_options();
359
		}
360
361
		return $args;
362
	}
363
364
	/**
365
	 * Return Total number of objects.
366
	 *
367
	 * @param array $config Full Sync config.
368
	 *
369
	 * @return int total
370
	 */
371
	public function total( $config ) {
372
		return count( Defaults::get_options_whitelist() );
373
	}
374
375
}
376