1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* @package Freemius |
4
|
|
|
* @copyright Copyright (c) 2015, Freemius, Inc. |
5
|
|
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3 |
6
|
|
|
* @since 1.2.3 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
10
|
|
|
exit; |
11
|
|
|
} |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Class FS_Options |
15
|
|
|
* |
16
|
|
|
* A wrapper class for handling network level and single site level options. |
17
|
|
|
*/ |
18
|
|
|
class FS_Options { |
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var string |
21
|
|
|
*/ |
22
|
|
|
private $_id; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var array[string]FS_Options { |
26
|
|
|
* @key string |
27
|
|
|
* @value FS_Options |
28
|
|
|
* } |
29
|
|
|
*/ |
30
|
|
|
private static $_instances; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var FS_Option_Manager Site level options. |
34
|
|
|
*/ |
35
|
|
|
private $_options; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var FS_Option_Manager Network level options. |
39
|
|
|
*/ |
40
|
|
|
private $_network_options; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var int The ID of the blog that is associated with the current site level options. |
44
|
|
|
*/ |
45
|
|
|
private $_blog_id = 0; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var bool |
49
|
|
|
*/ |
50
|
|
|
private $_is_multisite; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var string[] Lazy collection of params on the site level. |
54
|
|
|
*/ |
55
|
|
|
private static $_SITE_OPTIONS_MAP; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @author Leo Fajardo (@leorw) |
59
|
|
|
* @since 2.0.0 |
60
|
|
|
* |
61
|
|
|
* @param string $id |
62
|
|
|
* @param bool $load |
63
|
|
|
* |
64
|
|
|
* @return FS_Options |
65
|
|
|
*/ |
66
|
|
|
static function instance( $id, $load = false ) { |
|
|
|
|
67
|
|
|
if ( ! isset( self::$_instances[ $id ] ) ) { |
68
|
|
|
self::$_instances[ $id ] = new FS_Options( $id, $load ); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
return self::$_instances[ $id ]; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @author Leo Fajardo (@leorw) |
76
|
|
|
* @since 2.0.0 |
77
|
|
|
* |
78
|
|
|
* @param string $id |
79
|
|
|
* @param bool $load |
80
|
|
|
*/ |
81
|
|
|
private function __construct( $id, $load = false ) { |
82
|
|
|
$this->_id = $id; |
83
|
|
|
$this->_is_multisite = is_multisite(); |
84
|
|
|
|
85
|
|
|
if ( $this->_is_multisite ) { |
86
|
|
|
$this->_blog_id = get_current_blog_id(); |
87
|
|
|
$this->_network_options = FS_Option_Manager::get_manager( $id, $load, true ); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
$this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id ); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Switch the context of the site level options manager. |
95
|
|
|
* |
96
|
|
|
* @author Vova Feldman (@svovaf) |
97
|
|
|
* @since 2.0.0 |
98
|
|
|
* |
99
|
|
|
* @param $blog_id |
100
|
|
|
*/ |
101
|
|
|
function set_site_blog_context( $blog_id ) { |
|
|
|
|
102
|
|
|
$this->_blog_id = $blog_id; |
103
|
|
|
|
104
|
|
|
$this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id ); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @author Leo Fajardo (@leorw) |
109
|
|
|
* |
110
|
|
|
* @param string $option |
111
|
|
|
* @param mixed $default |
112
|
|
|
* @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). |
113
|
|
|
* |
114
|
|
|
* @return mixed |
115
|
|
|
*/ |
116
|
|
|
function get_option( $option, $default = null, $network_level_or_blog_id = null ) { |
|
|
|
|
117
|
|
|
if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { |
118
|
|
|
return $this->_network_options->get_option( $option, $default ); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$site_options = $this->get_site_options( $network_level_or_blog_id ); |
|
|
|
|
122
|
|
|
|
123
|
|
|
return $site_options->get_option( $option, $default ); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @author Leo Fajardo (@leorw) |
128
|
|
|
* @since 2.0.0 |
129
|
|
|
* |
130
|
|
|
* @param string $option |
131
|
|
|
* @param mixed $value |
132
|
|
|
* @param bool $flush |
133
|
|
|
* @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). |
134
|
|
|
*/ |
135
|
|
|
function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) { |
|
|
|
|
136
|
|
|
if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { |
137
|
|
|
$this->_network_options->set_option( $option, $value, $flush ); |
138
|
|
|
} else { |
139
|
|
|
$site_options = $this->get_site_options( $network_level_or_blog_id ); |
|
|
|
|
140
|
|
|
$site_options->set_option( $option, $value, $flush ); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @author Vova Feldman (@svovaf) |
146
|
|
|
* @since 2.0.0 |
147
|
|
|
* |
148
|
|
|
* @param string $option |
149
|
|
|
* @param bool $flush |
150
|
|
|
* @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). |
151
|
|
|
*/ |
152
|
|
|
function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) { |
|
|
|
|
153
|
|
|
if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { |
154
|
|
|
$this->_network_options->unset_option( $option, $flush ); |
155
|
|
|
} else { |
156
|
|
|
$site_options = $this->get_site_options( $network_level_or_blog_id ); |
|
|
|
|
157
|
|
|
$site_options->unset_option( $option, $flush ); |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @author Leo Fajardo (@leorw) |
163
|
|
|
* @since 2.0.0 |
164
|
|
|
* |
165
|
|
|
* @param bool $flush |
166
|
|
|
* @param bool $network_level |
167
|
|
|
*/ |
168
|
|
|
function load( $flush = false, $network_level = true ) { |
|
|
|
|
169
|
|
|
if ( $this->_is_multisite && $network_level ) { |
170
|
|
|
$this->_network_options->load( $flush ); |
171
|
|
|
} else { |
172
|
|
|
$this->_options->load( $flush ); |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @author Leo Fajardo (@leorw) |
178
|
|
|
* @since 2.0.0 |
179
|
|
|
* |
180
|
|
|
* @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage. |
181
|
|
|
*/ |
182
|
|
|
function store( $network_level_or_blog_id = null ) { |
|
|
|
|
183
|
|
|
if ( ! $this->_is_multisite || |
184
|
|
|
false === $network_level_or_blog_id || |
185
|
|
|
0 == $network_level_or_blog_id || |
186
|
|
|
is_null( $network_level_or_blog_id ) |
187
|
|
|
) { |
188
|
|
|
$site_options = $this->get_site_options( $network_level_or_blog_id ); |
|
|
|
|
189
|
|
|
$site_options->store(); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
if ( $this->_is_multisite && |
193
|
|
|
( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id ) |
194
|
|
|
) { |
195
|
|
|
$this->_network_options->store(); |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* @author Vova Feldman (@svovaf) |
201
|
|
|
* @since 2.0.0 |
202
|
|
|
* |
203
|
|
|
* @param int|null|bool $network_level_or_blog_id |
204
|
|
|
* @param bool $flush |
205
|
|
|
*/ |
206
|
|
|
function clear( $network_level_or_blog_id = null, $flush = false ) { |
|
|
|
|
207
|
|
|
if ( ! $this->_is_multisite || |
208
|
|
|
false === $network_level_or_blog_id || |
209
|
|
|
is_null( $network_level_or_blog_id ) || |
210
|
|
|
is_numeric( $network_level_or_blog_id ) |
211
|
|
|
) { |
212
|
|
|
$site_options = $this->get_site_options( $network_level_or_blog_id ); |
|
|
|
|
213
|
|
|
$site_options->clear( $flush ); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
if ( $this->_is_multisite && |
217
|
|
|
( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) |
218
|
|
|
) { |
219
|
|
|
$this->_network_options->clear( $flush ); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Migration script to the new storage data structure that is network compatible. |
225
|
|
|
* |
226
|
|
|
* IMPORTANT: |
227
|
|
|
* This method should be executed only after it is determined if this is a network |
228
|
|
|
* level compatible product activation. |
229
|
|
|
* |
230
|
|
|
* @author Vova Feldman (@svovaf) |
231
|
|
|
* @since 2.0.0 |
232
|
|
|
* |
233
|
|
|
* @param int $blog_id |
234
|
|
|
*/ |
235
|
|
|
function migrate_to_network( $blog_id = 0 ) { |
|
|
|
|
236
|
|
|
if ( ! $this->_is_multisite ) { |
237
|
|
|
return; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
$updated = false; |
241
|
|
|
|
242
|
|
|
$site_options = $this->get_site_options( $blog_id ); |
243
|
|
|
|
244
|
|
|
$keys = $site_options->get_options_keys(); |
245
|
|
|
|
246
|
|
|
foreach ( $keys as $option ) { |
247
|
|
|
if ( $this->is_site_option( $option ) || |
248
|
|
|
// Don't move admin notices to the network storage. |
249
|
|
|
in_array($option, array( |
250
|
|
|
// Don't move admin notices to the network storage. |
251
|
|
|
'admin_notices', |
252
|
|
|
// Don't migrate the module specific data, it will be migrated by the FS_Storage. |
253
|
|
|
'plugin_data', |
254
|
|
|
'theme_data', |
255
|
|
|
)) |
256
|
|
|
) { |
257
|
|
|
continue; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$option_updated = false; |
261
|
|
|
|
262
|
|
|
// Migrate option to the network storage. |
263
|
|
|
$site_option = $site_options->get_option( $option ); |
264
|
|
|
|
265
|
|
|
if ( ! $this->_network_options->has_option( $option ) ) { |
266
|
|
|
// Option not set on the network level, so just set it. |
267
|
|
|
$this->_network_options->set_option( $option, $site_option, false ); |
268
|
|
|
|
269
|
|
|
$option_updated = true; |
270
|
|
|
} else { |
271
|
|
|
// Option already set on the network level, so we need to merge it inelegantly. |
272
|
|
|
$network_option = $this->_network_options->get_option( $option ); |
273
|
|
|
|
274
|
|
|
if ( is_array( $network_option ) && is_array( $site_option ) ) { |
275
|
|
|
// Option is an array. |
276
|
|
|
foreach ( $site_option as $key => $value ) { |
277
|
|
|
if ( ! isset( $network_option[ $key ] ) ) { |
278
|
|
|
$network_option[ $key ] = $value; |
279
|
|
|
|
280
|
|
|
$option_updated = true; |
281
|
|
|
} else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) { |
282
|
|
|
if ( empty( $network_option[ $key ] ) ) { |
283
|
|
|
$network_option[ $key ] = $value; |
284
|
|
|
|
285
|
|
|
$option_updated = true; |
286
|
|
|
} else if ( empty( $value ) ) { |
|
|
|
|
287
|
|
|
// Do nothing. |
288
|
|
|
} else { |
289
|
|
|
reset($value); |
290
|
|
|
$first_key = key($value); |
291
|
|
|
if ( $value[$first_key] instanceof FS_Entity ) { |
292
|
|
|
// Merge entities by IDs. |
293
|
|
|
$network_entities_ids = array(); |
294
|
|
|
foreach ( $network_option[ $key ] as $entity ) { |
295
|
|
|
$network_entities_ids[ $entity->id ] = true; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
foreach ( $value as $entity ) { |
299
|
|
|
if ( ! isset( $network_entities_ids[ $entity->id ] ) ) { |
300
|
|
|
$network_option[ $key ][] = $entity; |
301
|
|
|
|
302
|
|
|
$option_updated = true; |
303
|
|
|
} |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
if ( $option_updated ) { |
312
|
|
|
$this->_network_options->set_option( $option, $network_option, false ); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Remove the option from site level storage. |
318
|
|
|
* |
319
|
|
|
* IMPORTANT: |
320
|
|
|
* The line below is intentionally commented since we want to preserve the option |
321
|
|
|
* on the site storage level for "downgrade compatibility". Basically, if the user |
322
|
|
|
* will downgrade to an older version of the plugin with the prev storage structure, |
323
|
|
|
* it will continue working. |
324
|
|
|
* |
325
|
|
|
* @todo After a few releases we can remove this. |
326
|
|
|
*/ |
327
|
|
|
// $site_options->unset_option($option, false); |
|
|
|
|
328
|
|
|
|
329
|
|
|
if ( $option_updated ) { |
330
|
|
|
$updated = true; |
331
|
|
|
} |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
if ( ! $updated ) { |
335
|
|
|
return; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
// Update network level storage. |
339
|
|
|
$this->_network_options->store(); |
340
|
|
|
// $site_options->store(); |
|
|
|
|
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
|
344
|
|
|
#-------------------------------------------------------------------------------- |
345
|
|
|
#region Helper Methods |
346
|
|
|
#-------------------------------------------------------------------------------- |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* We don't want to load the map right away since it's not even needed in a non-MS environment. |
350
|
|
|
* |
351
|
|
|
* @author Vova Feldman (@svovaf) |
352
|
|
|
* @since 2.0.0 |
353
|
|
|
*/ |
354
|
|
|
private static function load_site_options_map() { |
355
|
|
|
self::$_SITE_OPTIONS_MAP = array( |
|
|
|
|
356
|
|
|
'sites' => true, |
357
|
|
|
'theme_sites' => true, |
358
|
|
|
'unique_id' => true, |
359
|
|
|
'active_plugins' => true, |
360
|
|
|
); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* @author Vova Feldman (@svovaf) |
365
|
|
|
* @since 2.0.0 |
366
|
|
|
* |
367
|
|
|
* @param string $option |
368
|
|
|
* |
369
|
|
|
* @return bool |
370
|
|
|
*/ |
371
|
|
|
private function is_site_option( $option ) { |
372
|
|
|
if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) { |
373
|
|
|
return false; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) { |
377
|
|
|
self::load_site_options_map(); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
return isset( self::$_SITE_OPTIONS_MAP[ $option ] ); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* @author Vova Feldman (@svovaf) |
385
|
|
|
* @since 2.0.0 |
386
|
|
|
* |
387
|
|
|
* @param int $blog_id |
388
|
|
|
* |
389
|
|
|
* @return FS_Option_Manager |
390
|
|
|
*/ |
391
|
|
|
private function get_site_options( $blog_id = 0 ) { |
392
|
|
|
if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { |
393
|
|
|
return $this->_options; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
return FS_Option_Manager::get_manager( $this->_id, true, $blog_id ); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Check if an option should be stored on the MS network storage. |
401
|
|
|
* |
402
|
|
|
* @author Vova Feldman (@svovaf) |
403
|
|
|
* @since 2.0.0 |
404
|
|
|
* |
405
|
|
|
* @param string $option |
406
|
|
|
* @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS). |
407
|
|
|
* |
408
|
|
|
* @return bool |
409
|
|
|
*/ |
410
|
|
|
private function should_use_network_storage( $option, $network_level_or_blog_id = null ) { |
411
|
|
|
if ( ! $this->_is_multisite ) { |
412
|
|
|
// Not a multisite environment. |
413
|
|
|
return false; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
if ( is_numeric( $network_level_or_blog_id ) ) { |
417
|
|
|
// Explicitly asked to use a specified blog storage. |
418
|
|
|
return false; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
if ( is_bool( $network_level_or_blog_id ) ) { |
422
|
|
|
// Explicitly specified whether should use the network or blog level storage. |
423
|
|
|
return $network_level_or_blog_id; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
// Determine which storage to use based on the option. |
427
|
|
|
return ! $this->is_site_option( $option ); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
#endregion |
431
|
|
|
} |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.