PluginProbe
Customify / 2.10.9
Customify v2.10.9
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.9, at includes/class-pixcustomify.php

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