PluginProbe
Customify / 2.7.0
Customify v2.7.0
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-customify-style-manager.php

class-customify-style-manager.php in Customify 2.7.0, at includes/class-customify-style-manager.php

950 lines 32.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This is the class that handles the overall logic for the Style Manager.
4 *
5 * @see https://pixelgrade.com
6 * @author Pixelgrade
7 * @since 1.7.0
8 */
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly
12 }
13
14 if ( ! class_exists( 'Customify_Style_Manager' ) ) {
15
16 class Customify_Style_Manager {
17
18 /**
19 * Holds the only instance of this class.
20 * @var null|Customify_Style_Manager
21 * @access protected
22 * @since 1.7.0
23 */
24 protected static $_instance = null;
25
26 /**
27 * The main plugin object (the parent).
28 * @var null|PixCustomifyPlugin
29 * @access public
30 * @since 1.7.0
31 */
32 public $parent = null;
33
34 /**
35 * The external theme configs object.
36 * @var null|Customify_Theme_Configs
37 * @access public
38 * @since 1.7.4
39 */
40 protected $theme_configs = null;
41
42 /**
43 * The color palettes object.
44 * @var null|Customify_Color_Palettes
45 * @access public
46 * @since 1.7.4
47 */
48 protected $color_palettes = null;
49
50 /**
51 * The font palettes object.
52 * @var null|Customify_Font_Palettes
53 * @access public
54 * @since 1.7.4
55 */
56 protected $font_palettes = null;
57
58 /**
59 * The cloud fonts object.
60 * @var null|Customify_Cloud_Fonts
61 * @access public
62 * @since 2.7.0
63 */
64 protected $cloud_fonts = null;
65
66 /**
67 * The Cloud API object.
68 * @var null|Customify_Cloud_Api
69 * @access public
70 * @since 1.7.4
71 */
72 protected $cloud_api = null;
73
74 /**
75 * Cache for the wupdates identification data to avoid firing the filter multiple times.
76 * @var array
77 * @access protected
78 */
79 protected static $wupdates_ids = array();
80
81 /**
82 * Constructor.
83 *
84 * @since 1.7.0
85 */
86 protected function __construct() {
87 $this->init();
88 }
89
90 /**
91 * Initialize this module.
92 *
93 * @since 1.7.4
94 */
95 public function init() {
96 /**
97 * Initialize the Themes Config logic.
98 */
99 require_once 'class-customify-theme-configs.php';
100 $this->theme_configs = Customify_Theme_Configs::instance();
101
102 /**
103 * Initialize the Color Palettes logic.
104 */
105 require_once 'class-customify-color-palettes.php';
106 $this->color_palettes = Customify_Color_Palettes::instance();
107
108 /**
109 * Initialize the Font Palettes logic.
110 */
111 require_once 'class-customify-font-palettes.php';
112 $this->font_palettes = Customify_Font_Palettes::instance();
113
114 /**
115 * Initialize the Cloud Fonts logic.
116 */
117 require_once 'class-customify-cloud-fonts.php';
118 $this->cloud_fonts = Customify_Cloud_Fonts::instance();
119
120 /**
121 * Initialize the Cloud API logic.
122 */
123 require_once 'lib/class-customify-cloud-api.php';
124 $this->cloud_api = new Customify_Cloud_Api();
125
126 // Make sure that the Design Assets class is loaded.
127 require_once 'lib/class-customify-design-assets.php';
128
129 // Hook up.
130 $this->add_hooks();
131 }
132
133 /**
134 * Initiate our hooks
135 *
136 * @since 1.7.0
137 */
138 public function add_hooks() {
139 /*
140 * Handle the Customizer Style Manager base config.
141 */
142 add_filter( 'customify_filter_fields', array( $this, 'style_manager_section_base_config' ), 12, 1 );
143
144 /*
145 * Handle the grouping and reorganization of the Customizer theme sections when Style Manager is active.
146 */
147 add_filter( 'customify_final_config', array( $this, 'reorganize_customify_sections' ), 10, 1 );
148 // Remove the switch theme panel from the Customizer.
149 add_action( 'customize_register', array( $this, 'remove_switch_theme_panel' ), 12 );
150 // Add the logic that handles sections and controls registered directly to WP_Customizer, not through the Customify config.
151 add_action( 'customize_register', array( $this, 'reorganize_direct_sections_and_controls' ), 998 );
152
153 /*
154 * Handle other, more general reorganization in the Customizer, independent of Style Manager.
155 */
156 add_action( 'customize_register', array( $this, 'general_reorganization_of_customize_sections' ), 999, 1 );
157
158 /*
159 * Handle the customization of controls based on theme type.
160 */
161 add_filter( 'customify_filter_fields', array( $this, 'pre_filter_based_on_theme_type' ), 20, 1 );
162 add_filter( 'customify_final_config', array( $this, 'filter_based_on_theme_type' ), 20, 1 );
163 add_action( 'customify_after_preset_control', array( $this, 'maybe_add_text_after_color_palettes' ), 10, 1 );
164 add_action( 'customify_after_sm_palette_filter_control', array( $this, 'maybe_add_text_after_color_palette_filters' ), 10, 1 );
165 add_action( 'customify_after_sm_radio_control', array( $this, 'maybe_add_text_after_color_palettes_customize_controls' ), 10, 1 );
166
167 /*
168 * Handle the logic for user feedback.
169 */
170 add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_user_feedback_modal' ) );
171 add_action( 'wp_ajax_customify_style_manager_user_feedback', array( $this, 'user_feedback_callback' ) );
172
173 add_filter( 'customify_localized_js_settings', array( $this, 'add_to_localized_data' ), 10, 1 );
174
175 /*
176 * Scripts enqueued in the Customizer.
177 */
178 add_action( 'customize_controls_init', array( $this, 'register_admin_customizer_scripts' ), 10 );
179 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_admin_customizer_scripts' ), 10 );
180 }
181
182 /**
183 * Register Customizer admin scripts.
184 */
185 function register_admin_customizer_scripts() {
186 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
187
188 wp_register_script( PixCustomifyPlugin()->get_slug() . '-style-manager',
189 plugins_url( 'js/customizer/style-manager' . $suffix . '.js', PixCustomifyPlugin()->get_file() ),
190 array( 'jquery' ), PixCustomifyPlugin()->get_version() );
191 }
192
193 /**
194 * Enqueue Customizer admin scripts
195 */
196 function enqueue_admin_customizer_scripts() {
197 // If there is no style manager support, bail early.
198 if ( ! $this->is_supported() ) {
199 return;
200 }
201
202 // Enqueue the needed scripts, already registered.
203 wp_enqueue_script( PixCustomifyPlugin()->get_slug() . '-style-manager' );
204 }
205
206 /**
207 * Determine if Style Manager is supported.
208 *
209 * @return bool
210 * @since 1.7.0
211 *
212 */
213 public function is_supported() {
214 $has_support = (bool) current_theme_supports( 'customizer_style_manager' );
215
216 return apply_filters( 'customify_style_manager_is_supported', $has_support );
217 }
218
219 /**
220 * Setup the Style Manager Customizer section base config.
221 *
222 * This handles the base configuration for the controls in the Style Manager section. We expect other parties (e.g. the theme),
223 * to come and fill up the missing details (e.g. connected fields).
224 *
225 * @param array $config This holds required keys for the plugin config like 'opt-name', 'panels', 'settings'
226 *
227 * @return array
228 * @since 1.7.0
229 *
230 */
231 public function style_manager_section_base_config( $config ) {
232 // If there is no style manager support, bail early.
233 if ( ! $this->is_supported() ) {
234 return $config;
235 }
236
237 if ( ! isset( $config['sections']['style_manager_section'] ) ) {
238 $config['sections']['style_manager_section'] = array();
239 }
240
241 // The section might be already defined, thus we merge, not replace the entire section config.
242 $config['sections']['style_manager_section'] = Customify_Array::array_merge_recursive_distinct( $config['sections']['style_manager_section'], array(
243 'title' => esc_html__( 'Style Manager', 'customify' ),
244 'section_id' => 'style_manager_section',
245 // We will force this section id preventing prefixing and other regular processing.
246 'priority' => 1,
247 'options' => array(),
248 ) );
249
250 return $config;
251 }
252
253 /**
254 * Reorganize the Customizer sections.
255 *
256 * @param array $config This holds required keys for the plugin config like 'opt-name', 'panels', 'settings'.
257 *
258 * @return array
259 * @since 1.7.4
260 *
261 */
262 public function reorganize_customify_sections( $config ) {
263 // If there is no style manager support, bail early.
264 if ( ! $this->is_supported() ) {
265 return $config;
266 }
267
268 // If there is no Style Manager section or panel, bail.
269 if ( ! isset( $config['sections']['style_manager_section'] ) &&
270 ! isset( $config['panels']['style_manager_panel'] ) ) {
271
272 return $config;
273 }
274
275 $style_manager_section_config = false;
276 if ( isset( $config['sections']['style_manager_section'] ) ) {
277 $style_manager_section_config = $config['sections']['style_manager_section'];
278 unset( $config['sections']['style_manager_section'] );
279 }
280
281 // All the other sections.
282 $other_theme_sections_config = $config['sections'];
283 unset( $config['sections'] );
284
285 // The Style Manager panel.
286 if ( empty( $config['panels']['style_manager_panel'] ) ) {
287 $style_manager_panel_config = array(
288 'priority' => 22, // after the Site Identity panel
289 'capability' => 'edit_theme_options',
290 'panel_id' => 'style_manager_panel',
291 'title' => esc_html__( 'Style Manager', 'customify' ),
292 'description' => wp_kses_post( __( '<strong>Style Manager</strong> is an intuitive system to help you change the look of your website and make an excellent impression.', 'customify' ) ),
293 'sections' => array(),
294 'auto_expand_sole_section' => true, // If there is only one section in the panel, auto-expand it.
295 );
296 } else {
297 $style_manager_panel_config = $config['panels']['style_manager_panel'];
298 unset( $config['panels']['style_manager_panel'] );
299 }
300
301 $other_panels_config = false;
302 if ( ! empty( $config['panels'] ) ) {
303 $other_panels_config = $config['panels'];
304 unset( $config['panels'] );
305 }
306
307 // Maybe handle the color palettes.
308 if ( is_array( $style_manager_section_config ) && class_exists( 'Customify_Color_Palettes' ) && Customify_Color_Palettes::instance()->is_supported() ) {
309
310 // We need to split the fields in the Style Manager section into two: color palettes and fonts.
311 $color_palettes_fields = array(
312 'sm_current_color_palette',
313 'sm_palettes_description',
314 'sm_filters_description',
315 'sm_customize_description',
316 'sm_color_matrix',
317 'sm_palette_filter',
318 'sm_coloration_level',
319 'sm_color_diversity',
320 'sm_shuffle_colors',
321 'sm_dark_mode',
322 'sm_dark_color_master_slider',
323 'sm_dark_color_primary_slider',
324 'sm_dark_color_secondary_slider',
325 'sm_dark_color_tertiary_slider',
326 'sm_colors_dispersion',
327 'sm_colors_focus_point',
328 'sm_color_palette',
329 'sm_color_palette_variation',
330 'sm_color_primary',
331 'sm_color_primary_final',
332 'sm_color_secondary',
333 'sm_color_secondary_final',
334 'sm_color_tertiary',
335 'sm_color_tertiary_final',
336 'sm_dark_primary',
337 'sm_dark_primary_final',
338 'sm_dark_secondary',
339 'sm_dark_secondary_final',
340 'sm_dark_tertiary',
341 'sm_dark_tertiary_final',
342 'sm_light_primary',
343 'sm_light_primary_final',
344 'sm_light_secondary',
345 'sm_light_secondary_final',
346 'sm_light_tertiary',
347 'sm_light_tertiary_final',
348 'sm_swap_colors',
349 'sm_swap_dark_light',
350 'sm_swap_colors_dark',
351 'sm_swap_secondary_colors_dark',
352 'sm_advanced_toggle',
353 'sm_spacing_bottom',
354 );
355
356 $color_palettes_section_config = array(
357 'title' => esc_html__( 'Colors', 'customify' ),
358 'section_id' => 'sm_color_palettes_section',
359 'priority' => 10,
360 'options' => array(),
361 );
362 foreach ( $color_palettes_fields as $field_id ) {
363 if ( ! isset( $style_manager_section_config['options'][ $field_id ] ) ) {
364 continue;
365 }
366
367 if ( empty( $color_palettes_section_config['options'] ) ) {
368 $color_palettes_section_config['options'] = array( $field_id => $style_manager_section_config['options'][ $field_id ] );
369 } else {
370 $color_palettes_section_config['options'] = array_merge( $color_palettes_section_config['options'], array( $field_id => $style_manager_section_config['options'][ $field_id ] ) );
371 }
372 }
373
374 $style_manager_panel_config['sections']['sm_color_palettes_section'] = $color_palettes_section_config;
375 }
376
377 // Maybe handle the font palettes.
378 if ( is_array( $style_manager_section_config ) && class_exists( 'Customify_Font_Palettes' ) && Customify_Font_Palettes::instance()->is_supported() ) {
379
380 $font_palettes_fields = array(
381 'sm_font_palette',
382 'sm_font_palette_variation',
383 'sm_font_primary',
384 'sm_font_secondary',
385 'sm_font_body',
386 'sm_font_accent',
387 'sm_swap_fonts',
388 'sm_swap_primary_secondary_fonts',
389 );
390
391 $font_palettes_section_config = array(
392 'title' => esc_html__( 'Fonts', 'customify' ),
393 'section_id' => 'sm_font_palettes_section',
394 'priority' => 20,
395 'options' => array(),
396 );
397 foreach ( $font_palettes_fields as $field_id ) {
398 if ( ! isset( $style_manager_section_config['options'][ $field_id ] ) ) {
399 continue;
400 }
401
402 if ( empty( $font_palettes_section_config['options'] ) ) {
403 $font_palettes_section_config['options'] = array( $field_id => $style_manager_section_config['options'][ $field_id ] );
404 } else {
405 $font_palettes_section_config['options'] = array_merge( $font_palettes_section_config['options'], array( $field_id => $style_manager_section_config['options'][ $field_id ] ) );
406 }
407 }
408
409 $style_manager_panel_config['sections']['sm_font_palettes_section'] = $font_palettes_section_config;
410 }
411
412 // Start fresh and add the Style Manager panel config
413 if ( empty( $config['panels'] ) ) {
414 $config['panels'] = array();
415 }
416 $config['panels']['style_manager_panel'] = $style_manager_panel_config;
417
418 // The Theme Options panel.
419 $theme_options_panel_config = array(
420 'priority' => 24, // after the Style Manager panel.
421 'capability' => 'edit_theme_options',
422 'panel_id' => 'theme_options_panel',
423 'title' => esc_html__( 'Theme Options', 'customify' ),
424 'description' => esc_html__( 'Advanced options to change your site look-and-feel on a detailed level.', 'customify' ),
425 'sections' => array(),
426 );
427
428 // If we have other panels we will make their sections parts of the Theme Options panel.
429 if ( ! empty( $other_panels_config ) ) {
430 // If we have another panel that is called Theme Options we will extract it's sections and put them directly in the Theme Options panel.
431 $second_theme_options_sections = array();
432 foreach ( $other_panels_config as $panel_id => $panel_config ) {
433 $found = false;
434 // First try the panel ID.
435 if ( false !== strpos( strtolower( str_replace( '-', '_', $panel_id ) ), 'theme_options' ) ) {
436 $found = true;
437 }
438
439 // Second, try the panel title.
440 if ( ! $found && ! empty( $panel_config['title'] ) && false !== strpos( strtolower( str_replace( array(
441 '-',
442 '_'
443 ), ' ', $panel_config['title'] ) ), ' theme options' ) ) {
444 $found = true;
445 }
446
447 if ( $found && ! empty( $panel_config['sections'] ) ) {
448 $second_theme_options_sections = array_merge( $second_theme_options_sections, $panel_config['sections'] );
449 unset( $other_panels_config[ $panel_id ] );
450 }
451 }
452 if ( ! empty( $second_theme_options_sections ) ) {
453 $theme_options_panel_config['sections'] = array_merge( $theme_options_panel_config['sections'], $second_theme_options_sections );
454 }
455
456 // For the remaining panels, we will put their section into the Theme Options panel, but prefix their title with their respective panel title.
457 $prefixed_sections = array();
458 foreach ( $other_panels_config as $panel_id => $panel_config ) {
459 if ( ! empty( $panel_config['sections'] ) ) {
460 foreach ( $panel_config['sections'] as $section_id => $section_config ) {
461 if ( ! empty( $section_config['title'] ) && ! empty( $panel_config['title'] ) ) {
462 $section_config['title'] = $panel_config['title'] . ' - ' . $section_config['title'];
463 }
464 $prefixed_sections[ $panel_id . '_' . $section_id ] = $section_config;
465 }
466 }
467 }
468 $theme_options_panel_config['sections'] = array_merge( $theme_options_panel_config['sections'], $prefixed_sections );
469 }
470
471 // If we have other sections we will add them to the Theme Options panel.
472 if ( ! empty( $other_theme_sections_config ) ) {
473 $theme_options_panel_config['sections'] = array_merge( $theme_options_panel_config['sections'], $other_theme_sections_config );
474 }
475
476 if ( empty( $config['panels']['theme_options_panel'] ) ) {
477 $config['panels']['theme_options_panel'] = $theme_options_panel_config;
478 } else {
479 $config['panels']['theme_options_panel'] = array_merge( $config['panels']['theme_options_panel'], $theme_options_panel_config );
480 }
481
482 return $config;
483 }
484
485 /**
486 * Reorganize the Customizer sections and panels, without accounting for Customify's configured ones.
487 *
488 * @param WP_Customize_Manager $wp_customize WP_Customize_Manager instance.
489 */
490 public function general_reorganization_of_customize_sections( $wp_customize ) {
491 $sections = $wp_customize->sections();
492 if ( ! empty( $sections['pro__section'] ) ) {
493 $sections['pro__section']->priority = 24; // After the Style Manager panel.
494 }
495
496 // Add a pretty icon to Site Identity
497 $wp_customize->get_section( 'title_tagline' )->title = '&#x1f465; ' . esc_html__( 'Site Identity', 'customify' );
498 }
499
500 /**
501 * Filter the config during the build up.
502 *
503 * @param array $config
504 *
505 * @return array
506 */
507 public function pre_filter_based_on_theme_type( $config ) {
508 if ( in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) {
509
510 add_filter( 'customify_style_manager_color_palettes_colors_classes', function ( $classes ) {
511 $classes[] = 'js-no-picker';
512
513 return $classes;
514 } );
515 }
516
517 return $config;
518 }
519
520 public function maybe_add_text_after_color_palettes( $control ) {
521 if ( 'sm_color_palette' === $control->setting->id && in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) { ?>
522 <li id="customize-control-sm_palettes_description_after_control" class="pix_customizer_setting customize-control customize-control-html" style="display: list-item;">
523 <span class="description customize-control-description"><strong>Many more color palettes</strong> are available with the PRO version of your theme.</span>
524 </li>
525 <?php }
526 }
527
528 public function maybe_add_text_after_color_palette_filters( $control ) {
529 if ( in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) { ?>
530 <li id="customize-control-sm_filters_description_after_control" class="pix_customizer_setting customize-control customize-control-html" style="display: list-item;">
531 <span class="description customize-control-description"><strong>More filters</strong> are available with the PRO version of your theme.</span>
532 </li>
533 <?php }
534 }
535
536 public function maybe_add_text_after_color_palettes_customize_controls( $control ) {
537 if ( 'sm_coloration_level' === $control->setting->id && in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) { ?>
538 <li id="customize-control-sm_customize_description_after_control" class="pix_customizer_setting customize-control customize-control-html" style="display: list-item;">
539 <span class="description customize-control-description"><strong>More options</strong> are available with the PRO version of your theme.</span>
540 </li>
541 <?php }
542 }
543
544 /**
545 * Filter the final config.
546 *
547 * @param array $config
548 *
549 * @return array
550 */
551 public function filter_based_on_theme_type( $config ) {
552 if ( ! empty( $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section']['options'] ) && in_array( self::get_theme_type(), array(
553 'theme_wporg',
554 'theme_modular_wporg'
555 ) ) ) {
556 $color_palettes_options = $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section']['options'];
557
558 $options_to_remove = array(
559 'sm_color_diversity',
560 'sm_shuffle_colors',
561 'sm_dark_mode',
562 );
563 foreach ( $options_to_remove as $option_key ) {
564 if ( isset( $color_palettes_options[ $option_key ] ) ) {
565 $color_palettes_options[ $option_key ]['type'] = 'hidden_control';
566 }
567 }
568
569 if ( ! empty( $color_palettes_options['sm_palette_filter']['choices'] ) ) {
570 unset( $color_palettes_options['sm_palette_filter']['choices']['clarendon'] );
571 unset( $color_palettes_options['sm_palette_filter']['choices']['pastel'] );
572 unset( $color_palettes_options['sm_palette_filter']['choices']['greyish'] );
573 }
574
575 $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section']['options'] = $color_palettes_options;
576 }
577
578 return $config;
579 }
580
581 /**
582 * Get the current theme type from the WUpdates code.
583 *
584 * Generally, this is a 'theme', but it could also be 'plugin', 'theme_modular', 'theme_wporg' or other markers we wish to use.
585 *
586 * @return string
587 */
588 public static function get_theme_type() {
589 $wupdates_identification = self::get_wupdates_identification_data();
590 if ( empty( $wupdates_identification['type'] ) ) {
591 return 'theme_wporg';
592 }
593
594 return sanitize_title( $wupdates_identification['type'] );
595 }
596
597 public static function get_wupdates_identification_data( $slug = '' ) {
598 if ( empty( $slug ) ) {
599 $slug = basename( get_template_directory() );
600 }
601
602 $wupdates_ids = self::get_all_wupdates_identification_data();
603
604 // We really want an id (hash_id) and a type.
605 if ( empty( $slug ) || empty( $wupdates_ids[ $slug ] ) || ! isset( $wupdates_ids[ $slug ]['id'] ) || ! isset( $wupdates_ids[ $slug ]['type'] ) ) {
606 return false;
607 }
608
609 return $wupdates_ids[ $slug ];
610 }
611
612 public static function get_all_wupdates_identification_data() {
613 if ( empty( self::$wupdates_ids ) ) {
614 self::$wupdates_ids = apply_filters( 'wupdates_gather_ids', array() );
615 }
616
617 return self::$wupdates_ids;
618 }
619
620 /**
621 * Reorganizes sections and controls added directly to WP_Customizer, not through the config.
622 *
623 * @param WP_Customize_Manager $wp_customize
624 *
625 * @since 1.9.0
626 *
627 * @todo Please note that this is house cleaning and it is only necessary due to the lack of complete standardization on the theme side. We should not need this forever!
628 *
629 */
630 public function reorganize_direct_sections_and_controls( $wp_customize ) {
631 // If there is no style manager support, bail.
632 if ( ! $this->is_supported() ) {
633 return;
634 }
635
636 $theme_options_panel = $wp_customize->get_panel( 'theme_options_panel' );
637 // Bail if we don't have a Theme Options panel since all that we do at the moment is related to it.
638 if ( empty( $theme_options_panel ) ) {
639 return;
640 }
641
642 /** @var WP_Customize_Section $section */
643 foreach ( $wp_customize->sections() as $section ) {
644 // These are general theme options sections that need to have their controls moved to the Theme Options > General section.
645 if ( false !== strpos( $section->id, 'theme_options' ) ) {
646 // Find the General theme options section, if any.
647 $general_section = false;
648 foreach ( $theme_options_panel->sections as $theme_options_section ) {
649 if ( false !== strpos( $theme_options_section->id, 'general' ) ) {
650 $general_section = $section;
651 }
652 }
653
654 if ( false === $general_section ) {
655 // We need to add a general section in the Theme Options panel.
656 $general_section = $wp_customize->add_section( 'theme_options[general]', array(
657 'title' => esc_html__( 'General', 'customify' ),
658 'panel' => $theme_options_panel->id,
659 'priority' => 2,
660 ) );
661 }
662
663 // Move all the controls in the identified theme options section to the general one.
664 /** @var WP_Customize_Control $control */
665 foreach ( $wp_customize->controls() as $control ) {
666 if ( $control->section !== $section->id ) {
667 continue;
668 }
669
670 $control->section = $general_section->id;
671 }
672
673 // Finally remove the now empty section.
674 $wp_customize->remove_section( $section->id );
675
676 break;
677 }
678 }
679 }
680
681 /**
682 * Remove the switch/preview theme panel.
683 *
684 * @param WP_Customize_Manager $wp_customize
685 *
686 * @since 1.7.4
687 *
688 */
689 public function remove_switch_theme_panel( $wp_customize ) {
690 // If there is no style manager support, bail.
691 if ( ! $this->is_supported() ) {
692 return;
693 }
694
695 $wp_customize->remove_panel( 'themes' );
696 }
697
698 /**
699 * Add data to be available in JS.
700 *
701 * @since 2.7.0
702 *
703 * @param $localized
704 *
705 * @return mixed
706 */
707 public function add_to_localized_data( $localized ) {
708 if ( empty( $localized['styleManager'] ) ) {
709 $localized['styleManager'] = array();
710 }
711
712 $localized['styleManager']['userFeedback'] = array(
713 'nonce' => wp_create_nonce( 'customify_style_manager_user_feedback' ),
714 'provided' => get_option( 'style_manager_user_feedback_provided', false ),
715 );
716
717 return $localized;
718 }
719
720 /**
721 * Output the user feedback modal markup, if we need to.
722 *
723 * @since 1.7.0
724 */
725 public function output_user_feedback_modal() {
726 // If there is no style manager support, bail early.
727 if ( ! $this->is_supported() ) {
728 return;
729 }
730
731 // We want to ask for feedback once a month.
732 $a_month_back = time() - MONTH_IN_SECONDS;
733
734 // Only output if we should ask for feedback.
735 if ( $this->should_ask_for_feedback( $a_month_back ) ) { ?>
736 <div id="style-manager-user-feedback-modal">
737 <div class="modal">
738 <div class="modal-dialog" role="document">
739 <div class="modal-content">
740 <form id="style-manager-user-feedback" action="#" method="post">
741 <input type="hidden" name="type" value="1_to_5"/>
742 <div class="modal-header">
743 <button type="button" class="close icon media-modal-close" data-dismiss="modal"
744 aria-label="Close"><span class="media-modal-icon"><span
745 class="screen-reader-text">Close media panel</span></span></button>
746 <!-- <a href="#" class="close button button--naked gray" data-dismiss="modal" aria-label="Close">Close</a> -->
747 </div>
748 <div class="modal-body full">
749 <div class="box box--large">
750 <div class="first-step">
751 <h2 class="modal-title">How would you rate your experience in finding the right colors for your site?</h2>
752 <div class="scorecard">
753 <span>Poor</span>
754 <label>
755 <input type="radio" name="rating" value="1" required/>
756 <span>1</span>
757 </label>
758 <label>
759 <input type="radio" name="rating" value="2" required/>
760 <span>2</span>
761 </label>
762 <label>
763 <input type="radio" name="rating" value="3" required/>
764 <span>3</span>
765 </label>
766 <label>
767 <input type="radio" name="rating" value="4" required/>
768 <span>4</span>
769 </label>
770 <label>
771 <input type="radio" name="rating" value="5" required/>
772 <span>5</span>
773 </label>
774 <span>Great</span>
775 </div>
776 </div>
777 <div class="second-step hidden">
778 <p><strong>What points along the way made this a <span
779 class="rating-placeholder">5</span>* experience for
780 you?</strong><br>We are counting on your insights to guide us in
781 doing better 🙏</p>
782 <div class="not-floating-labels">
783 <div class="form-row field">
784 <textarea name="message"
785 placeholder="Describe your experience in customizing your site colors.."
786 id="style-manager-user-feedback-message" rows="6"
787 oninvalid="this.setCustomValidity('May we have a little more info about your experience?')"
788 oninput="setCustomValidity('')" required></textarea>
789 </div>
790 </div>
791 <button id="style-manager-user-feedback_btn" class="button"
792 type="submit"><?php _e( 'Send us your insights', 'customify' ); ?></button>
793 </div>
794 <div class="thanks-step hidden">
795 <h3 class="modal-title">Thank you so much for your feedback!</h3>
796 <p>It means the world to us as we strive to constantly push the limits
797 and aim higher. Stay awesome! 🤗</p>
798 <p><em>The Pixelgrade Team</em></p>
799 </div>
800 <div class="error-step hidden">
801 <h3 class="modal-title">We've hit a snag!</h3>
802 <p>We couldn't record your feedback and we would truly appreciate it if
803 you would try it again at a latter time. Stay awesome! 🤗</p>
804 </div>
805 </div>
806 </div>
807 <div class="modal-footer full">
808
809 </div>
810 </form>
811 </div>
812 </div>
813 </div>
814 <!-- End Modal -->
815 <!-- Modal Backdrop (Shadow) -->
816 <div class="modal-backdrop"></div>
817 </div>
818
819 <?php }
820 }
821
822 /**
823 * Return whether user provided feedback, and if so, return the timestamp.
824 *
825 * @return bool|int
826 */
827 public function user_provided_feedback() {
828 $user_provided_feedback = get_option( 'style_manager_user_feedback_provided' );
829 if ( empty( $user_provided_feedback ) ) {
830 return false;
831 }
832
833 return $user_provided_feedback;
834 }
835
836 /**
837 * Determine if we should ask for user feedback.
838 *
839 * @param bool|int $timestamp_limit Optional. Timestamp to compare the time the user provided feedback.
840 * If the provided timestamp is earlier than the time the user provided feedback, should ask again.
841 *
842 * @return bool
843 */
844 public function should_ask_for_feedback( $timestamp_limit = false ) {
845 if ( defined( 'CUSTOMIFY_SM_ALWAYS_ASK_FOR_FEEDBACK' ) && true === CUSTOMIFY_SM_ALWAYS_ASK_FOR_FEEDBACK ) {
846 return true;
847 }
848
849 $feedback_timestamp = $this->user_provided_feedback();
850 if ( empty( $feedback_timestamp ) ) {
851 return true;
852 }
853
854 if ( ! empty( $timestamp_limit ) && intval( $timestamp_limit ) > intval( $feedback_timestamp ) ) {
855 return true;
856 }
857
858 return false;
859 }
860
861 /**
862 * Callback for the user feedback AJAX call.
863 *
864 * @since 1.7.0
865 */
866 public function user_feedback_callback() {
867 check_ajax_referer( 'customify_style_manager_user_feedback', 'nonce' );
868
869 if ( empty( $_POST['type'] ) ) {
870 wp_send_json_error( esc_html__( 'No type provided', 'customify' ) );
871 }
872
873 if ( empty( $_POST['rating'] ) ) {
874 wp_send_json_error( esc_html__( 'No rating provided', 'customify' ) );
875 }
876
877 $type = sanitize_text_field( $_POST['type'] );
878 $rating = intval( $_POST['rating'] );
879 $message = '';
880 if ( ! empty( $_POST['message'] ) ) {
881 $message = wp_kses_post( $_POST['message'] );
882 }
883
884 $request_data = array(
885 'site_url' => home_url( '/' ),
886 'satisfaction_data' => array(
887 'type' => $type,
888 'rating' => $rating,
889 'message' => $message,
890 ),
891 );
892
893 // Send the feedback.
894 $response = $this->cloud_api->send_stats( $request_data, true );
895 if ( is_wp_error( $response ) ) {
896 wp_send_json_error( esc_html__( 'Sorry, something went wrong and we couldn\'t save your feedback.', 'customify' ) );
897 }
898 $response_data = json_decode( wp_remote_retrieve_body( $response ), true );
899 // Bail in case of decode error or failure to retrieve data
900 if ( null === $response_data || empty( $response_data['code'] ) || 'success' !== $response_data['code'] ) {
901 wp_send_json_error( esc_html__( 'Sorry, something went wrong and we couldn\'t save your feedback.', 'customify' ) );
902 }
903
904 // We need to remember that the user provided feedback (and at what timestamp).
905 update_option( 'style_manager_user_feedback_provided', time(), true );
906
907 wp_send_json_success( esc_html__( 'Thank you for your feedback.', 'customify' ) );
908 }
909
910 /**
911 * Main Customify_Style_Manager Instance
912 *
913 * Ensures only one instance of Customify_Style_Manager is loaded or can be loaded.
914 *
915 * @return Customify_Style_Manager Main Customify_Style_Manager instance
916 * @since 1.7.0
917 * @static
918 *
919 */
920 public static function instance() {
921
922 if ( is_null( self::$_instance ) ) {
923 self::$_instance = new self();
924 }
925
926 return self::$_instance;
927 }
928
929 /**
930 * Cloning is forbidden.
931 *
932 * @since 1.7.0
933 */
934 public function __clone() {
935
936 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
937 }
938
939 /**
940 * Unserializing instances of this class is forbidden.
941 *
942 * @since 1.7.0
943 */
944 public function __wakeup() {
945
946 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
947 }
948 }
949 }
950