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-customify-style-manager.php

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

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