PluginProbe
Customify / 2.10.1
Customify v2.10.1
2.10.9 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.7.1 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.6.0 1.6.0.1 1.6.5 1.7.0 1.7.1 All 77 releases
customify / includes / class-pixcustomify.php

class-pixcustomify.php in Customify 2.10.1, at includes/class-pixcustomify.php

1,046 lines 31.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PixCustomify.
4 * @package PixCustomify
5 * @author Pixelgrade <contact@pixelgrade.com>
6 * @license GPL-2.0+
7 * @link https://pixelgrade.com
8 * @copyright 2014-2020 Pixelgrade
9 */
10
11 /**
12 * Main plugin class.
13 * @package PixCustomify
14 * @author Pixelgrade <contact@pixelgrade.com>
15 */
16 class PixCustomifyPlugin {
17
18 /**
19 * Plugin version, used for cache-busting of style and script file references.
20 * @since 1.5.0
21 * @const string
22 */
23 protected $_version;
24 /**
25 * Unique identifier for your plugin.
26 * Use this value (not the variable name) as the text domain when internationalizing strings of text. It should
27 * match the Text Domain file header in the main plugin file.
28 * @since 1.0.0
29 * @var string
30 */
31 protected $plugin_slug = 'customify';
32
33 /**
34 * Instance of this class.
35 * @since 1.5.0
36 * @var object
37 */
38 protected static $_instance = null;
39
40 /**
41 * The main plugin file.
42 * @var string
43 * @access public
44 * @since 1.5.0
45 */
46 public $file;
47
48 /**
49 * Settings class object.
50 * @var Customify_Settings
51 * @access public
52 * @since 2.4.0
53 */
54 public $settings = null;
55
56 /**
57 * Customizer class object to handle customizer controls and logic.
58 * @var PixCustomify_Customizer
59 * @access public
60 * @since 2.4.0
61 */
62 public $customizer = null;
63
64 /**
65 * Fonts class object to handle fonts global logic.
66 * @var Customify_Fonts_Global
67 * @access public
68 * @since 2.7.0
69 */
70 public $fonts_global = null;
71
72 /**
73 * Style Manager class object.
74 * @var Customify_Style_Manager
75 * @access public
76 * @since 1.0.0
77 */
78 public $style_manager = null;
79
80 /**
81 * Block Editor class object.
82 * @var Customify_Block_Editor
83 * @access public
84 * @since 2.7.0
85 */
86 public $block_editor = null;
87
88 /**
89 * Classic Editor class object.
90 * @var Customify_Classic_Editor
91 * @access public
92 * @since 2.7.0
93 */
94 public $classic_editor = null;
95
96 /**
97 * Customizer Search class object.
98 * @var Customify_Customizer_Search
99 * @access public
100 * @since 2.9.0
101 */
102 public $customizer_search = null;
103
104 protected $options_minimal_details = array();
105 protected $options_details = array();
106
107 protected $opt_name;
108
109 private $customizer_config = array();
110
111 /**
112 * Minimal Required PHP Version
113 * @var string
114 * @access private
115 * @since 1.5.0
116 */
117 private $minimalRequiredPhpVersion = '5.4';
118
119 protected function __construct( $file, $version = '1.0.0' ) {
120 // The main plugin file (the one that loads all this).
121 $this->file = $file;
122 // The current plugin version.
123 $this->_version = $version;
124
125 if ( $this->php_version_check() ) {
126 // Only load and run the init function if we know PHP version can parse it.
127 $this->init();
128 }
129 }
130
131 /**
132 * Initialize plugin
133 */
134 private function init() {
135 // Handle the install and uninstall logic
136 register_activation_hook( $this->get_file(), array( 'PixCustomifyPlugin', 'install' ) );
137
138 /* Initialize the plugin settings logic. */
139 require_once( $this->get_base_path() . 'includes/class-customify-settings.php' );
140 if ( is_null( $this->settings ) ) {
141 $this->settings = Customify_Settings::instance( $this->get_file(), $this->get_slug(), $this->get_version() );
142 }
143
144 /* Initialize the Customizer logic. */
145 require_once( $this->get_base_path() . 'includes/class-customify-customizer.php' );
146 if ( is_null( $this->customizer ) ) {
147 $this->customizer = PixCustomify_Customizer::instance();
148 }
149
150 /* Initialize the Fonts logic. */
151 require_once( $this->get_base_path() . 'includes/class-customify-fonts-global.php' );
152 if ( is_null( $this->fonts_global ) ) {
153 $this->fonts_global = Customify_Fonts_Global::instance();
154 }
155
156 /* Initialize the Style Manager logic. */
157 require_once( $this->get_base_path() . 'includes/class-customify-style-manager.php' );
158 if ( is_null( $this->style_manager ) ) {
159 $this->style_manager = Customify_Style_Manager::instance();
160 }
161
162 /* Initialize the Block Editor integration logic. */
163 require_once( $this->get_base_path() . 'includes/class-customify-block-editor.php' );
164 if ( is_null( $this->block_editor ) ) {
165 $this->block_editor = Customify_Block_Editor::instance();
166 }
167
168 /* Initialize the Classic Editor integration logic. */
169 require_once( $this->get_base_path() . 'includes/class-customify-classic-editor.php' );
170 if ( is_null( $this->classic_editor ) ) {
171 $this->classic_editor = Customify_Classic_Editor::instance();
172 }
173
174 /* Initialize the Customizer Search logic. */
175 require_once( $this->get_base_path() . 'includes/class-customify-customizer-search.php' );
176 if ( is_null( $this->customizer_search ) ) {
177 $this->customizer_search = Customify_Customizer_Search::instance();
178 }
179
180 // Register all the needed hooks
181 $this->register_hooks();
182 }
183
184 /**
185 * Register our actions and filters
186 */
187 function register_hooks() {
188
189 /*
190 * Load plugin text domain
191 */
192 add_action( 'init', array( $this, 'load_plugin_textdomain' ) );
193
194 /*
195 * Load the upgrade logic.
196 */
197 add_action( 'admin_init', array( $this, 'upgrade' ) );
198
199 /*
200 * Handle the force clearing of the caches. We clear in a proactive manner.
201 */
202 add_action( 'activated_plugin', array( $this, 'invalidate_all_caches' ), 1 );
203 add_action( 'deactivated_plugin', array( $this, 'invalidate_all_caches' ), 1 );
204 add_action( 'after_switch_theme', array( $this, 'invalidate_all_caches' ), 1 );
205 add_action( 'upgrader_process_complete', array( $this, 'invalidate_all_caches' ), 1 );
206
207 // Whenever we update data from the Customizer, we will invalidate the options details (that include the value).
208 // Customize save (publish) used the same changeset save logic, so this filter is fired then also.
209 add_filter( 'customize_changeset_save_data', array( $this, 'filter_invalidate_options_details_cache' ), 50, 1 );
210 }
211
212 /**
213 * Handle the logic to upgrade between versions. It will run only one per version change.
214 */
215 public function upgrade() {
216 $customify_dbversion = get_option( 'customify_dbversion', '0.0.1' );
217 if ( $this->get_version() === $customify_dbversion ) {
218 return;
219 }
220
221 // For versions, previous of version 2.0.0 (the Color Palettes v2.0 release).
222 if ( version_compare( $customify_dbversion, '2.0.0', '<' ) ) {
223 // Delete the option holding the fact that the user offered feedback.
224 delete_option( 'style_manager_user_feedback_provided' );
225 }
226
227 // Put the current version in the database.
228 update_option( 'customify_dbversion', $this->get_version(), true );
229
230 $this->invalidate_all_caches();
231 }
232
233 /**
234 * Invalidate all caches.
235 *
236 * @since 2.6.0
237 */
238 public function invalidate_all_caches() {
239 $this->invalidate_customizer_config_cache();
240 $this->invalidate_options_details_cache();
241 $this->invalidate_customizer_opt_name_cache();
242 $this->invalidate_options_details_cache();
243
244 do_action( 'customify_invalidate_all_caches' );
245 }
246
247 /**
248 * Invalidate all caches, when hooked via a filter (just pass through the value).
249 *
250 * @since 2.6.0
251 *
252 * @param mixed $value
253 * @return mixed
254 */
255 public function filter_invalidate_all_caches( $value ) {
256 $this->invalidate_all_caches();
257
258 return $value;
259 }
260
261 /**
262 * This will clear any instance properties that are used as local cache during a request to avoid
263 * fetching the data from DB on each method call.
264 *
265 * This may be called during a request when something happens that (potentially) invalidates our data mid-request.
266 */
267 public function clear_locally_cached_data() {
268 $this->opt_name = null;
269
270 $this->customizer_config = null;
271
272 $this->options_minimal_details = null;
273 $this->options_details = null;
274 }
275
276 public function get_options_key( $skip_cache = false ) {
277 if ( ! empty( $this->opt_name ) ) {
278 return $this->opt_name;
279 }
280
281 if ( $this->should_force_skip_cache() ) {
282 $skip_cache = true;
283 }
284
285 // First try and get the cached data
286 $data = get_option( $this->get_customizer_opt_name_cache_key() );
287 $expire_timestamp = false;
288
289 // Only try to get the expire timestamp if we really need to.
290 if ( true !== $skip_cache && false !== $data ) {
291 // Get the cache data expiration timestamp.
292 $expire_timestamp = get_option( $this->get_customizer_opt_name_cache_key() . '_timestamp' );
293 }
294
295 // The data isn't set, is expired or we were instructed to skip the cache; we need to regenerate the config.
296 if ( true === $skip_cache || false === $data || false === $expire_timestamp || $expire_timestamp < time() ) {
297
298 $data = $this->get_customizer_config( 'opt-name' );
299
300 if ( true !== $skip_cache ) {
301 // Cache the data in an option for 24 hours, but only if we are not supposed to skip the cache entirely.
302 update_option( $this->get_customizer_opt_name_cache_key(), $data, true );
303 update_option( $this->get_customizer_opt_name_cache_key() . '_timestamp', time() + 24 * HOUR_IN_SECONDS, true );
304 }
305 }
306
307 $this->opt_name = $data;
308 return $data;
309 }
310
311 private function get_customizer_opt_name_cache_key() {
312 return 'customify_customizer_opt_name';
313 }
314
315 public function invalidate_customizer_opt_name_cache() {
316 update_option( $this->get_customizer_opt_name_cache_key() . '_timestamp' , time() - 24 * HOUR_IN_SECONDS, true );
317
318 $this->clear_locally_cached_data();
319 }
320
321 public function filter_invalidate_customizer_opt_name_cache( $value ) {
322 $this->invalidate_customizer_opt_name_cache();
323
324 return $value;
325 }
326
327
328 public function get_options_details( $only_minimal_details = false, $skip_cache = false ) {
329
330 // If we already have the data, do as little as possible.
331 if ( true === $only_minimal_details && ! empty( $this->options_minimal_details ) ) {
332 return $this->options_minimal_details;
333 }
334 if ( ! empty( $this->options_details ) ) {
335 return $this->options_details;
336 }
337
338 if ( $this->should_force_skip_cache() ) {
339 $skip_cache = true;
340 }
341
342 // We will first look for cached data
343
344 $data = $this->options_minimal_details = get_option( $this->get_options_minimal_details_cache_key() );
345 if ( false !== $data && false === $only_minimal_details ) {
346 $extra_details_data = get_option( $this->get_options_extra_details_cache_key() );
347 if ( is_array( $extra_details_data ) ) {
348 $data = $this->options_details = Customify_Array::array_merge_recursive_distinct( $data, $extra_details_data );
349 } else {
350 // Something is wrong with the extra details and we need to regenerate.
351 $this->invalidate_options_details_cache();
352 }
353 }
354
355 // For performance reasons, we will use the cached data (even if stale)
356 // when a user is not logged in or a user without administrative capabilities is logged in.
357 if ( false !== $data && false === $skip_cache && ! current_user_can( 'manage_options' ) ) {
358 return $data;
359 }
360
361 $expire_timestamp = false;
362
363 // Only try to get the expire timestamp if we really need to.
364 if ( true !== $skip_cache && false !== $data ) {
365 // Get the cached data expiration timestamp.
366 $expire_timestamp = get_option( $this->get_options_details_cache_timestamp_key() );
367 }
368
369 // The data isn't set, is expired or we were instructed to skip the cache; we need to regenerate the config.
370 if ( true === $skip_cache || false === $data || false === $expire_timestamp || $expire_timestamp < time() ) {
371 $options_minimal_details = array();
372 $options_extra_details = array();
373
374 $minimal_detail_keys = array(
375 'type',
376 'setting_type',
377 'setting_id',
378 'default',
379 'css',
380 'output',
381 'value',
382 'selector',
383 'callback',
384 'active_callback',
385 );
386
387 $customizer_config = $this->get_customizer_config();
388
389 if ( isset ( $customizer_config['panels'] ) ) {
390 foreach ( $customizer_config['panels'] as $pane_id => $panel_settings ) {
391 if ( isset( $panel_settings['sections'] ) ) {
392 foreach ( $panel_settings['sections'] as $section_id => $section_settings ) {
393 if ( isset( $section_settings['options'] ) ) {
394 foreach ( $section_settings['options'] as $option_id => $option_config ) {
395 if ( is_array( $option_config ) ) {
396 foreach ( $option_config as $key => $value ) {
397 if ( in_array( $key, $minimal_detail_keys ) ) {
398 $options_minimal_details[ $option_id ][ $key ] = $value;
399 } else {
400 $options_extra_details[ $option_id ][ $key ] = $value;
401 }
402 }
403
404 $options_minimal_details[ $option_id ]['value'] = $this->get_option( $option_id, null, $option_config );
405 }
406 }
407 }
408 }
409 }
410 }
411 }
412
413 if ( isset ( $customizer_config['sections'] ) ) {
414 foreach ( $customizer_config['sections'] as $section_id => $section_settings ) {
415 if ( isset( $section_settings['options'] ) ) {
416 foreach ( $section_settings['options'] as $option_id => $option_config ) {
417 if ( is_array( $option_config ) ) {
418 foreach ( $option_config as $key => $value ) {
419 if ( in_array( $key, $minimal_detail_keys ) ) {
420 $options_minimal_details[ $option_id ][ $key ] = $value;
421 } else {
422 $options_extra_details[ $option_id ][ $key ] = $value;
423 }
424 }
425
426 $options_minimal_details[ $option_id ]['value'] = $this->get_option( $option_id, null, $option_config );
427 }
428 }
429 }
430 }
431 }
432
433 if ( true !== $skip_cache ) {
434 // Cache the data for 24 hours, but only if we are not supposed to skip the cache entirely.
435 update_option( $this->get_options_minimal_details_cache_key(), $options_minimal_details, true );
436 update_option( $this->get_options_extra_details_cache_key(), $options_extra_details, false ); // we will not autoload extra details for performance reasons.
437 update_option( $this->get_options_details_cache_timestamp_key(), time() + 24 * HOUR_IN_SECONDS, true );
438 }
439
440 $data = $this->options_minimal_details = $options_minimal_details;
441 $this->options_details = Customify_Array::array_merge_recursive_distinct( $options_minimal_details, $options_extra_details );
442 if ( false === $only_minimal_details ) {
443 $data = $this->options_details;
444 }
445 }
446
447 return $data;
448 }
449
450 private function should_force_skip_cache() {
451 // If our development constant is defined and true, we will always skip the cache, except for AJAX calls.
452 // Other, more specific cases may impose skipping the cache also on AJAX calls.
453 if ( ! wp_doing_ajax()
454 && defined('CUSTOMIFY_ALWAYS_GENERATE_CUSTOMIZER_CONFIG' )
455 && true === CUSTOMIFY_ALWAYS_GENERATE_CUSTOMIZER_CONFIG ) {
456 return true;
457 }
458
459 // If we are in the Customizer and the request has a $_POST['customized'] parameter, we will skip the cache
460 // since this means that the preview is being reloaded with temporary settings values.
461 if ( ! empty( $_POST['customized'] ) ) {
462 return true;
463 }
464
465 // If we are currently previewing a theme without being actually active, we should not use cached data.
466
467 if ( ! empty( $_REQUEST['theme'] ) || ! empty( $_REQUEST['customize_theme'] ) ) {
468 return true;
469 }
470
471 /** @var WP_Customize_Manager $wp_customize */
472 global $wp_customize;
473 if ( ! empty( $wp_customize )
474 && method_exists( $wp_customize, 'is_theme_active' )
475 && ! $wp_customize->is_theme_active() ) {
476
477 return true;
478 }
479
480 return false;
481 }
482
483 private function get_options_minimal_details_cache_key() {
484 return 'customify_options_minimal_details';
485 }
486
487 private function get_options_extra_details_cache_key() {
488 return 'customify_options_extra_details';
489 }
490
491 private function get_options_details_cache_timestamp_key() {
492 return 'customify_options_details_timestamp';
493 }
494
495 public function invalidate_options_details_cache() {
496 update_option( $this->get_options_details_cache_timestamp_key(), time() - 24 * HOUR_IN_SECONDS, true );
497
498 $this->clear_locally_cached_data();
499 }
500
501 public function filter_invalidate_options_details_cache( $value ) {
502 $this->invalidate_options_details_cache();
503
504 return $value;
505 }
506
507 public function has_option( $option ) {
508
509 $options_details = $this->get_options_details(true);
510 if ( isset( $options_details[ $option ] ) ) {
511 return true;
512 }
513
514 return false;
515 }
516
517 public function get_customizer_config( $key = false ) {
518 $customizer_config = $this->load_customizer_config();
519
520 if ( false !== $key ) {
521 if ( is_array( $customizer_config ) && isset( $customizer_config[ $key ] ) ) {
522 return $customizer_config[ $key ];
523 }
524
525 return null;
526 }
527
528 return $customizer_config;
529 }
530
531 /**
532 * Set the customizer configuration.
533 *
534 * @since 2.2.1
535 *
536 * @param bool $skip_cache Optional. Whether to use the cached config or generate a new one.
537 * @return array
538 */
539 protected function load_customizer_config( $skip_cache = false ) {
540 if ( ! empty( $this->customizer_config ) ) {
541 return $this->customizer_config;
542 }
543
544 if ( $this->should_force_skip_cache() ) {
545 $skip_cache = true;
546 }
547
548 // First try and get the cached data
549 $data = get_option( $this->get_customizer_config_cache_key() );
550
551 // For performance reasons, we will use the cached data (even if stale)
552 // when a user is not logged in or a user without administrative capabilities is logged in.
553 if ( false !== $data && false === $skip_cache && ! current_user_can( 'manage_options' ) ) {
554 $this->customizer_config = $data;
555 return $data;
556 }
557
558 $expire_timestamp = false;
559
560 // Only try to get the expire timestamp if we really need to.
561 if ( true !== $skip_cache && false !== $data ) {
562 // Get the cache data expiration timestamp.
563 $expire_timestamp = get_option( $this->get_customizer_config_cache_key() . '_timestamp' );
564 }
565
566 // The data isn't set, is expired or we were instructed to skip the cache; we need to regenerate the config.
567 if ( true === $skip_cache || false === $data || false === $expire_timestamp || $expire_timestamp < time() ) {
568 // Allow themes or other plugins to filter the config.
569 $data = apply_filters( 'customify_filter_fields', array() );
570 // We apply a second filter for those that wish to work with the final config and not rely on a a huge priority number.
571 $data = apply_filters( 'customify_final_config', $data );
572
573 if ( true !== $skip_cache ) {
574 // Cache the data in an option for 24 hours, but only if we are not supposed to skip the cache entirely.
575 update_option( $this->get_customizer_config_cache_key(), $data, false );
576 update_option( $this->get_customizer_config_cache_key() . '_timestamp', time() + 24 * HOUR_IN_SECONDS, true );
577 }
578 }
579
580 $this->customizer_config = $data;
581
582 return $data;
583 }
584
585 private function get_customizer_config_cache_key() {
586 return 'customify_customizer_config';
587 }
588
589 public function invalidate_customizer_config_cache() {
590 update_option( $this->get_customizer_config_cache_key() . '_timestamp' , time() - 24 * HOUR_IN_SECONDS, true );
591
592 $this->clear_locally_cached_data();
593 }
594
595 /**
596 * Invalidate the customizer config cache, when hooked via a filter (just pass through the value).
597 *
598 * @since 2.4.0
599 *
600 * @param mixed $value
601 * @return mixed
602 */
603 public function filter_invalidate_customizer_config_cache( $value ) {
604 $this->invalidate_customizer_config_cache();
605
606 return $value;
607 }
608
609 /**
610 * Get the Customify configuration (and value, hence "details") of a certain option.
611 *
612 * @param string $option_id
613 * @param bool $minimal_details Optional. Whether to return only the minimum amount of details (mainly what is needed on the frontend).
614 * The advantage is that these details are cached, thus skipping the customizer_config!
615 * @param bool $skip_cache Optional.
616 *
617 * @return array|false The option config or false on failure.
618 */
619 public function get_option_details( $option_id, $minimal_details = false, $skip_cache = false ) {
620 if ( empty( $option_id ) ) {
621 return false;
622 }
623
624 $options_details = $this->get_options_details( $minimal_details, $skip_cache );
625 if ( ! empty( $options_details ) && is_array( $options_details ) && isset( $options_details[ $option_id ] ) ) {
626 return $options_details[ $option_id ];
627 }
628
629 return false;
630 }
631
632 /**
633 * This is just a wrapper for get_options_details() for backwards compatibility.
634 *
635 * @param bool $only_minimal_details
636 * @param bool $skip_cache
637 *
638 * @return array|mixed|void
639 */
640 public function get_options_configs( $only_minimal_details = false, $skip_cache = false ) {
641 return $this->get_options_details( $only_minimal_details, $skip_cache );
642 }
643
644 /**
645 * Get the value of a setting ID saved in a wp_options array entry.
646 *
647 * @param string $option_id This is only the option ID, that may differ from setting ID ( like in `body_font` vs `rosa_opt[body_font]`)
648 * @param string $setting_id We will use this to get the Customizer value, when in that context.
649 *
650 * @return mixed|null
651 */
652 protected function get_option_mod_value( $option_id, $setting_id ) {
653 global $wp_customize;
654
655 if ( empty( $option_id ) || empty( $setting_id ) ) {
656 return null;
657 }
658
659 if ( ! empty( $wp_customize ) && method_exists( $wp_customize, 'get_setting' ) ) {
660 $setting = $wp_customize->get_setting( $setting_id );
661 if ( ! empty( $setting ) ) {
662 return $setting->value();
663 }
664 }
665
666 $values = get_option( $this->get_options_key() );
667
668 if ( ! empty( $values ) && is_array( $values ) && isset( $values[ $option_id ] ) ) {
669 return $values[ $option_id ];
670 }
671
672 return null;
673 }
674
675 /**
676 * Get the value of a certain setting ID saved in the theme mod array.
677 *
678 * @param string $option_id This is only the option ID, that may differ from setting ID ( like in `body_font` vs `rosa_opt[body_font]`)
679 * @param string $setting_id We will use this to get the Customizer value, when in that context.
680 *
681 * @return mixed|null
682 */
683 protected function get_theme_mod_value( $option_id, $setting_id ) {
684 global $wp_customize;
685
686 if ( empty( $option_id ) || empty( $setting_id ) ) {
687 return null;
688 }
689
690 if ( ! empty( $wp_customize ) && method_exists( $wp_customize, 'get_setting' ) ) {
691 $setting = $wp_customize->get_setting( $setting_id );
692 if ( ! empty( $setting ) ) {
693 return $setting->value();
694 } elseif ( $wp_customize->is_preview() ) {
695 // If the setting is not registered (like in asking for the value before wp_loaded), we will read directly from the posted values via the changeset.
696 // Not really the best way, but ok.
697 $post_values = $wp_customize->unsanitized_post_values();
698 if ( array_key_exists( $setting_id, $post_values ) ) {
699 $value = $post_values[ $setting_id ];
700 // Skip validation and sanitization since it is too early.
701 if ( ! is_null( $value ) && ! is_wp_error( $value ) ) {
702 return $value;
703 }
704 }
705 }
706 }
707
708 $values = get_theme_mod( $this->get_options_key() );
709
710 if ( ! empty( $values ) && is_array( $values ) && isset( $values[ $option_id ] ) ) {
711 return $values[ $option_id ];
712 }
713
714 return null;
715 }
716
717 /**
718 * A public function to get an option's value.
719 * If there is a value and return it.
720 * Otherwise try to get the default parameter or the default from config.
721 *
722 * @param $option_id
723 * @param mixed $default Optional.
724 * @param array $option_details Optional.
725 *
726 * @return bool|null|string
727 */
728 public function get_option( $option_id, $default = null, $option_details = null ) {
729
730 if ( null === $option_details ) {
731 // Get the field config.
732 $option_details = $this->get_option_details( $option_id, true );
733 }
734
735 // If the development constant CUSTOMIFY_DEV_FORCE_DEFAULTS has been defined we will not retrieve anything from the database
736 // Always go with the default
737 if ( defined( 'CUSTOMIFY_DEV_FORCE_DEFAULTS' )
738 && true === CUSTOMIFY_DEV_FORCE_DEFAULTS
739 && ! $this->skip_dev_mode_force_defaults( $option_id, $option_details ) ) {
740
741 $value = null;
742 } else {
743
744 if ( empty( $option_id ) || empty( $option_details ) || ! is_array( $option_details ) ) {
745 $value = null;
746 } elseif ( isset( $option_details['value'] ) ) {
747 // If we already have the value cached in the option details, we will use that.
748 $value = $option_details['value'];
749 } else {
750 $value = null;
751
752 /*
753 * First determine the setting ID.
754 */
755 $setting_id = $this->get_options_key() . '[' . $option_id . ']';
756 // If we have been explicitly given a setting ID we will use that.
757 if ( ! empty( $option_details['setting_id'] ) ) {
758 $setting_id = $option_details['setting_id'];
759 }
760
761 /*
762 * Second, try to get the stored value of the setting.
763 */
764
765 // If we have a setting that directly declares it (not deduced like when registering fields in the Customizer)
766 // should be saved in the wp_options table, not in theme_mods, we will attempt to fetch it directly, first.
767 if ( isset( $option_details['setting_type'] ) && $option_details['setting_type'] === 'option' ) {
768 $value = get_option( $setting_id, null );
769 }
770
771 // If we don't have a value, we will grab the setting value from the array of values stored in either
772 // a wp_option entry or in the theme_mods.
773 // The "save as array" behavior happens even in the case of 'option' setting type if
774 // the setting ID is of the form 'rosa_option[some_key]' (aka a multidimensional setting ID).
775 if ( null === $value ) {
776 if ( ! empty( PixCustomifyPlugin()->settings ) && PixCustomifyPlugin()->settings->get_plugin_setting( 'values_store_mod' ) === 'option' ) {
777 // Get the value stored in a option.
778 $value = $this->get_option_mod_value( $option_id, $setting_id );
779 } else {
780 // Get the value stored in theme_mods.
781 $value = $this->get_theme_mod_value( $option_id, $setting_id );
782 }
783 }
784 }
785 }
786
787 // If we have a non-null value, return it.
788 if ( $value !== null ) {
789 return $value;
790 }
791
792 // If we have a non-null default, return it.
793 if ( $default !== null ) {
794 return $default;
795 }
796
797 // Finally, attempt to use the default value set in the config, if available.
798 if ( ! empty( $option_details ) && is_array( $option_details ) && isset( $option_details['default'] ) ) {
799 return $option_details['default'];
800 }
801
802 return null;
803 }
804
805 /**
806 * Determine if we should NOT enforce the CUSTOMIFY_DEV_FORCE_DEFAULTS behavior on a certain option.
807 *
808 * @param string $option_id
809 * @param array $option_config Optional.
810 *
811 * @return bool
812 */
813 public function skip_dev_mode_force_defaults( $option_id, $option_config = null ) {
814 // Preprocess the $option_id.
815 if ( false !== strpos( $option_id, '::' ) ) {
816 $option_id = substr( $option_id, strpos( $option_id, '::' ) + 2 );
817 }
818 if ( false !== strpos( $option_id, '[' ) ) {
819 $option_id = explode( '[', $option_id );
820 $option_id = rtrim( $option_id[1], ']' );
821 }
822
823 if ( null === $option_config ) {
824 $option_config = $this->get_option_details( $option_id, true );
825 }
826 if ( empty( $option_config ) || ! is_array( $option_config ) ) {
827 return false;
828 }
829
830 // We will skip certain field types that generally don't have a default value.
831 if ( ! empty( $option_config['type'] ) ) {
832 switch ( $option_config['type'] ) {
833 case 'cropped_image':
834 case 'cropped_media':
835 case 'image':
836 case 'media':
837 case 'custom_background':
838 case 'upload':
839 return true;
840 break;
841 default:
842 break;
843 }
844 }
845
846 return false;
847 }
848
849 public function get_version() {
850 return $this->_version;
851 }
852
853 public function get_slug() {
854 return $this->plugin_slug;
855 }
856
857 public function get_file() {
858 return $this->file;
859 }
860
861 public function get_base_path() {
862 return wp_normalize_path( plugin_dir_path( $this->file ) );
863 }
864
865 /**
866 * Load the plugin text domain for translation.
867 * @since 1.0.0
868 */
869 function load_plugin_textdomain() {
870 $domain = $this->plugin_slug;
871 load_plugin_textdomain( $domain, false, basename( dirname( $this->file ) ) . '/languages/' );
872 }
873
874 /**
875 * Checks whether an array is associative or not
876 *
877 * @param array $array
878 *
879 * @return bool
880 */
881 public function is_assoc( $array ) {
882
883 if ( ! is_array( $array ) ) {
884 return false;
885 }
886
887 // Keys of the array
888 $keys = array_keys( $array );
889
890 // If the array keys of the keys match the keys, then the array must
891 // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
892 return array_keys( $keys ) !== $keys;
893 }
894
895 /**
896 * Does the same thing the JS encodeURIComponent() does
897 *
898 * @param string $str
899 *
900 * @return string
901 */
902 public static function encodeURIComponent( $str ) {
903 //if we get an array we just let it be
904 if ( is_string( $str ) ) {
905 $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' );
906 $str = strtr( rawurlencode( $str ), $revert );
907 }
908
909 return $str;
910 }
911
912 /**
913 * Does the same thing the JS decodeURIComponent() does
914 *
915 * @param mixed $str
916 *
917 * @return mixed
918 */
919 public static function decodeURIComponent( $str ) {
920 // Nothing to do if we receive an array.
921 if ( is_array( $str ) ) {
922 return $str;
923 }
924
925 if ( is_string( $str ) ) {
926 $revert = array( '!' => '%21', '*' => '%2A', "'" => '%27', '(' => '%28', ')' => '%29' );
927 $str = rawurldecode( strtr( $str, $revert ) );
928 }
929
930 return $str;
931 }
932
933 /**
934 * Provide a useful error message when the user's PHP version is less than the required version
935 */
936 public function notice_php_version_wrong() {
937 $allowed = array(
938 'div' => array(
939 'class' => array(),
940 'id' => array(),
941 ),
942 'p' => array(),
943 'br' => array(),
944 'strong' => array(),
945 );
946 $html = '<div class="updated fade">' .
947 sprintf( esc_html__( 'Error: plugin "%s" requires a newer version of PHP to be running.', 'customify' ), 'Customify' ) .
948 '<br/>' . sprintf( esc_html__( 'Minimal version of PHP required: %s', 'customify' ), '<strong>' . $this->minimalRequiredPhpVersion . '</strong>' ) .
949 '<br/>' . sprintf( esc_html__( 'Your server\'s PHP version: %s', 'customify' ), '<strong>' . phpversion() . '</strong>' ) .
950 '</div>';
951 echo wp_kses( $html, $allowed );
952 }
953
954 /**
955 * PHP version check
956 */
957 protected function php_version_check() {
958
959 if ( version_compare( phpversion(), $this->minimalRequiredPhpVersion ) < 0 ) {
960 add_action( 'admin_notices', array( $this, 'notice_php_version_wrong' ) );
961
962 return false;
963 }
964
965 return true;
966 }
967
968 /*
969 * Install everything needed
970 */
971 static public function install() {
972 $config = Customify_Settings::get_plugin_config();
973
974 $defaults = array(
975
976 # Hidden fields
977 'settings_saved_once' => '0',
978 # General
979 'values_store_mod' => 'theme_mod',
980
981 'typography' => true,
982 'typography_system_fonts' => true,
983 'typography_google_fonts' => true,
984 'typography_group_google_fonts' => true,
985 'typography_cloud_fonts' => true,
986 'disable_default_sections' => array(),
987 'disable_customify_sections' => array(),
988 'enable_reset_buttons' => false,
989 'enable_editor_style' => true,
990 'style_resources_location' => 'wp_head'
991 );
992
993 $current_data = get_option( $config['settings-key'] );
994
995 if ( $current_data === false ) {
996 add_option( $config['settings-key'], $defaults );
997 } elseif ( count( array_diff_key( $defaults, $current_data ) ) != 0) {
998 $plugin_data = array_merge( $defaults, $current_data );
999 update_option( $config['settings-key'], $plugin_data );
1000 }
1001 }
1002
1003 /**
1004 * Main PixCustomifyPlugin Instance
1005 *
1006 * Ensures only one instance of PixCustomifyPlugin is loaded or can be loaded.
1007 *
1008 * @since 1.0.0
1009 * @static
1010 *
1011 * @param string $file File.
1012 * @param string $version Version.
1013 *
1014 * @see PixCustomifyPlugin()
1015 * @return PixCustomifyPlugin Main PixCustomifyPlugin instance
1016 */
1017 public static function instance( $file = '', $version = '1.0.0' ) {
1018 // If the single instance hasn't been set, set it now.
1019 if ( is_null( self::$_instance ) ) {
1020 self::$_instance = new self( $file, $version );
1021 }
1022
1023 return self::$_instance;
1024 }
1025
1026 /**
1027 * Cloning is forbidden.
1028 *
1029 * @since 1.5.0
1030 */
1031 public function __clone() {
1032
1033 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
1034 }
1035
1036 /**
1037 * Unserializing instances of this class is forbidden.
1038 *
1039 * @since 1.5.0
1040 */
1041 public function __wakeup() {
1042
1043 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
1044 }
1045 }
1046