PluginProbe
Customify / 2.10.7
Customify v2.10.7
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.7, at includes/class-customify-style-manager.php

957 lines 32.4 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_mode_advanced',
323 'sm_dark_color_master_slider',
324 'sm_dark_color_primary_slider',
325 'sm_dark_color_secondary_slider',
326 'sm_dark_color_tertiary_slider',
327 'sm_colors_dispersion',
328 'sm_colors_focus_point',
329 'sm_color_palette',
330 'sm_color_palette_variation',
331 'sm_color_primary',
332 'sm_color_primary_final',
333 'sm_color_secondary',
334 'sm_color_secondary_final',
335 'sm_color_tertiary',
336 'sm_color_tertiary_final',
337 'sm_dark_primary',
338 'sm_dark_primary_final',
339 'sm_dark_secondary',
340 'sm_dark_secondary_final',
341 'sm_dark_tertiary',
342 'sm_dark_tertiary_final',
343 'sm_light_primary',
344 'sm_light_primary_final',
345 'sm_light_secondary',
346 'sm_light_secondary_final',
347 'sm_light_tertiary',
348 'sm_light_tertiary_final',
349 'sm_swap_colors',
350 'sm_swap_dark_light',
351 'sm_swap_colors_dark',
352 'sm_swap_secondary_colors_dark',
353 'sm_advanced_toggle',
354 'sm_color_palettes_spacing_bottom',
355 );
356
357 $color_palettes_section_config = array(
358 'title' => esc_html__( 'Colors', 'customify' ),
359 'section_id' => 'sm_color_palettes_section',
360 'priority' => 10,
361 'options' => array(),
362 );
363 foreach ( $color_palettes_fields as $field_id ) {
364 if ( ! isset( $style_manager_section_config['options'][ $field_id ] ) ) {
365 continue;
366 }
367
368 if ( empty( $color_palettes_section_config['options'] ) ) {
369 $color_palettes_section_config['options'] = array( $field_id => $style_manager_section_config['options'][ $field_id ] );
370 } else {
371 $color_palettes_section_config['options'] = array_merge( $color_palettes_section_config['options'], array( $field_id => $style_manager_section_config['options'][ $field_id ] ) );
372 }
373 }
374
375 $style_manager_panel_config['sections']['sm_color_palettes_section'] = $color_palettes_section_config;
376 }
377
378 // Maybe handle the font palettes.
379 if ( is_array( $style_manager_section_config ) && class_exists( 'Customify_Font_Palettes' ) && Customify_Font_Palettes::instance()->is_supported() ) {
380
381 $font_palettes_fields = array(
382 'sm_current_font_palette',
383 'sm_font_palette',
384 'sm_font_palette_variation',
385 'sm_font_primary',
386 'sm_font_secondary',
387 'sm_font_body',
388 'sm_font_accent',
389 'sm_swap_fonts',
390 'sm_swap_primary_secondary_fonts',
391 'sm_font_palettes_spacing_bottom',
392 );
393
394 $font_palettes_section_config = array(
395 'title' => esc_html__( 'Fonts', 'customify' ),
396 'section_id' => 'sm_font_palettes_section',
397 'priority' => 20,
398 'options' => array(),
399 );
400 foreach ( $font_palettes_fields as $field_id ) {
401 if ( ! isset( $style_manager_section_config['options'][ $field_id ] ) ) {
402 continue;
403 }
404
405 if ( empty( $font_palettes_section_config['options'] ) ) {
406 $font_palettes_section_config['options'] = array( $field_id => $style_manager_section_config['options'][ $field_id ] );
407 } else {
408 $font_palettes_section_config['options'] = array_merge( $font_palettes_section_config['options'], array( $field_id => $style_manager_section_config['options'][ $field_id ] ) );
409 }
410 }
411
412 $style_manager_panel_config['sections']['sm_font_palettes_section'] = $font_palettes_section_config;
413 }
414
415 // Start fresh and add the Style Manager panel config
416 if ( empty( $config['panels'] ) ) {
417 $config['panels'] = array();
418 }
419 $config['panels']['style_manager_panel'] = $style_manager_panel_config;
420
421 // The Theme Options panel.
422 $theme_options_panel_config = array(
423 'priority' => 24, // after the Style Manager panel.
424 'capability' => 'edit_theme_options',
425 'panel_id' => 'theme_options_panel',
426 'title' => esc_html__( 'Theme Options', 'customify' ),
427 'description' => esc_html__( 'Advanced options to change your site look-and-feel on a detailed level.', 'customify' ),
428 'sections' => array(),
429 );
430
431 // If we have other panels we will make their sections parts of the Theme Options panel.
432 if ( ! empty( $other_panels_config ) ) {
433 // 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.
434 $second_theme_options_sections = array();
435 foreach ( $other_panels_config as $panel_id => $panel_config ) {
436 $found = false;
437 // First try the panel ID.
438 if ( false !== strpos( strtolower( str_replace( '-', '_', $panel_id ) ), 'theme_options' ) ) {
439 $found = true;
440 }
441
442 // Second, try the panel title.
443 if ( ! $found && ! empty( $panel_config['title'] ) && false !== strpos( strtolower( str_replace( array(
444 '-',
445 '_'
446 ), ' ', $panel_config['title'] ) ), ' theme options' ) ) {
447 $found = true;
448 }
449
450 if ( $found && ! empty( $panel_config['sections'] ) ) {
451 $second_theme_options_sections = array_merge( $second_theme_options_sections, $panel_config['sections'] );
452 unset( $other_panels_config[ $panel_id ] );
453 }
454 }
455 if ( ! empty( $second_theme_options_sections ) ) {
456 $theme_options_panel_config['sections'] = array_merge( $theme_options_panel_config['sections'], $second_theme_options_sections );
457 }
458
459 // For the remaining panels, we will put their section into the Theme Options panel, but prefix their title with their respective panel title.
460 $prefixed_sections = array();
461 foreach ( $other_panels_config as $panel_id => $panel_config ) {
462 if ( ! empty( $panel_config['sections'] ) ) {
463 foreach ( $panel_config['sections'] as $section_id => $section_config ) {
464 if ( ! empty( $section_config['title'] ) && ! empty( $panel_config['title'] ) ) {
465 $section_config['title'] = $panel_config['title'] . ' - ' . $section_config['title'];
466 }
467 $prefixed_sections[ $panel_id . '_' . $section_id ] = $section_config;
468 }
469 }
470 }
471 $theme_options_panel_config['sections'] = array_merge( $theme_options_panel_config['sections'], $prefixed_sections );
472 }
473
474 // If we have other sections we will add them to the Theme Options panel.
475 if ( ! empty( $other_theme_sections_config ) ) {
476 $theme_options_panel_config['sections'] = array_merge( $theme_options_panel_config['sections'], $other_theme_sections_config );
477 }
478
479 if ( empty( $config['panels']['theme_options_panel'] ) ) {
480 $config['panels']['theme_options_panel'] = $theme_options_panel_config;
481 } else {
482 $config['panels']['theme_options_panel'] = array_merge( $config['panels']['theme_options_panel'], $theme_options_panel_config );
483 }
484
485 return $config;
486 }
487
488 /**
489 * Reorganize the Customizer sections and panels, without accounting for Customify's configured ones.
490 *
491 * @param WP_Customize_Manager $wp_customize WP_Customize_Manager instance.
492 */
493 public function general_reorganization_of_customize_sections( $wp_customize ) {
494 $sections = $wp_customize->sections();
495 if ( ! empty( $sections['pro__section'] ) ) {
496 $sections['pro__section']->priority = 24; // After the Style Manager panel.
497 }
498
499 // Add a pretty icon to Site Identity
500 $wp_customize->get_section( 'title_tagline' )->title = '&#x1f465; ' . esc_html__( 'Site Identity', 'customify' );
501 }
502
503 /**
504 * Filter the config during the build up.
505 *
506 * @param array $config
507 *
508 * @return array
509 */
510 public function pre_filter_based_on_theme_type( $config ) {
511 if ( in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) {
512
513 add_filter( 'customify_style_manager_color_palettes_colors_classes', function ( $classes ) {
514 $classes[] = 'js-no-picker';
515
516 return $classes;
517 } );
518 }
519
520 return $config;
521 }
522
523 public function maybe_add_text_after_color_palettes( $control ) {
524 if ( 'sm_color_palette' === $control->setting->id && in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) { ?>
525 <li id="customize-control-sm_palettes_description_after_control" class="pix_customizer_setting customize-control customize-control-html" style="display: list-item;">
526 <span class="description customize-control-description"><strong>Many more color palettes</strong> are available with the PRO version of your theme.</span>
527 </li>
528 <?php }
529 }
530
531 public function maybe_add_text_after_color_palette_filters( $control ) {
532 if ( in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) { ?>
533 <li id="customize-control-sm_filters_description_after_control" class="pix_customizer_setting customize-control customize-control-html" style="display: list-item;">
534 <span class="description customize-control-description"><strong>More filters</strong> are available with the PRO version of your theme.</span>
535 </li>
536 <?php }
537 }
538
539 public function maybe_add_text_after_color_palettes_customize_controls( $control ) {
540 if ( 'sm_coloration_level' === $control->setting->id && in_array( self::get_theme_type(), array( 'theme_wporg', 'theme_modular_wporg' ) ) ) { ?>
541 <li id="customize-control-sm_customize_description_after_control" class="pix_customizer_setting customize-control customize-control-html" style="display: list-item;">
542 <span class="description customize-control-description"><strong>More options</strong> are available with the PRO version of your theme.</span>
543 </li>
544 <?php }
545 }
546
547 /**
548 * Filter the final config.
549 *
550 * @param array $config
551 *
552 * @return array
553 */
554 public function filter_based_on_theme_type( $config ) {
555 if ( ! empty( $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section']['options'] ) && in_array( self::get_theme_type(), array(
556 'theme_wporg',
557 'theme_modular_wporg'
558 ) ) ) {
559 $color_palettes_options = $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section']['options'];
560
561 $options_to_remove = array(
562 'sm_color_diversity',
563 'sm_shuffle_colors',
564 'sm_dark_mode',
565 );
566 foreach ( $options_to_remove as $option_key ) {
567 if ( isset( $color_palettes_options[ $option_key ] ) ) {
568 $color_palettes_options[ $option_key ]['type'] = 'hidden_control';
569 }
570 }
571
572 if ( ! empty( $color_palettes_options['sm_palette_filter']['choices'] ) ) {
573 unset( $color_palettes_options['sm_palette_filter']['choices']['clarendon'] );
574 unset( $color_palettes_options['sm_palette_filter']['choices']['pastel'] );
575 unset( $color_palettes_options['sm_palette_filter']['choices']['greyish'] );
576 }
577
578 $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section']['options'] = $color_palettes_options;
579 }
580
581 return $config;
582 }
583
584 /**
585 * Get the current theme type from the WUpdates code.
586 *
587 * Generally, this is a 'theme', but it could also be 'plugin', 'theme_modular', 'theme_wporg' or other markers we wish to use.
588 *
589 * @return string
590 */
591 public static function get_theme_type() {
592 $wupdates_identification = self::get_wupdates_identification_data();
593 if ( empty( $wupdates_identification['type'] ) ) {
594 return 'theme_wporg';
595 }
596
597 return sanitize_title( $wupdates_identification['type'] );
598 }
599
600 public static function get_wupdates_identification_data( $slug = '' ) {
601 if ( empty( $slug ) ) {
602 $slug = basename( get_template_directory() );
603 }
604
605 $wupdates_ids = self::get_all_wupdates_identification_data();
606
607 // We really want an id (hash_id) and a type.
608 if ( empty( $slug ) || empty( $wupdates_ids[ $slug ] ) || ! isset( $wupdates_ids[ $slug ]['id'] ) || ! isset( $wupdates_ids[ $slug ]['type'] ) ) {
609 return false;
610 }
611
612 return $wupdates_ids[ $slug ];
613 }
614
615 public static function get_all_wupdates_identification_data() {
616 if ( empty( self::$wupdates_ids ) ) {
617 self::$wupdates_ids = apply_filters( 'wupdates_gather_ids', array() );
618 }
619
620 return self::$wupdates_ids;
621 }
622
623 /**
624 * Reorganizes sections and controls added directly to WP_Customizer, not through the config.
625 *
626 * @param WP_Customize_Manager $wp_customize
627 *
628 * @since 1.9.0
629 *
630 * @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!
631 *
632 */
633 public function reorganize_direct_sections_and_controls( $wp_customize ) {
634 // If there is no style manager support, bail.
635 if ( ! $this->is_supported() ) {
636 return;
637 }
638
639 $theme_options_panel = $wp_customize->get_panel( 'theme_options_panel' );
640 // Bail if we don't have a Theme Options panel since all that we do at the moment is related to it.
641 if ( empty( $theme_options_panel ) ) {
642 return;
643 }
644
645 /** @var WP_Customize_Section $section */
646 foreach ( $wp_customize->sections() as $section ) {
647 // These are general theme options sections that need to have their controls moved to the Theme Options > General section.
648 if ( false !== strpos( $section->id, 'theme_options' ) ) {
649 // Find the General theme options section, if any.
650 $general_section = false;
651 foreach ( $theme_options_panel->sections as $theme_options_section ) {
652 if ( false !== strpos( $theme_options_section->id, 'general' ) ) {
653 $general_section = $section;
654 }
655 }
656
657 if ( false === $general_section ) {
658 // We need to add a general section in the Theme Options panel.
659 $general_section = $wp_customize->add_section( 'theme_options[general]', array(
660 'title' => esc_html__( 'General', 'customify' ),
661 'panel' => $theme_options_panel->id,
662 'priority' => 2,
663 ) );
664 }
665
666 // Move all the controls in the identified theme options section to the general one.
667 /** @var WP_Customize_Control $control */
668 foreach ( $wp_customize->controls() as $control ) {
669 if ( $control->section !== $section->id ) {
670 continue;
671 }
672
673 $control->section = $general_section->id;
674 }
675
676 // Finally remove the now empty section.
677 $wp_customize->remove_section( $section->id );
678
679 break;
680 }
681 }
682 }
683
684 /**
685 * Remove the switch/preview theme panel.
686 *
687 * @param WP_Customize_Manager $wp_customize
688 *
689 * @since 1.7.4
690 *
691 */
692 public function remove_switch_theme_panel( $wp_customize ) {
693 // If there is no style manager support, bail.
694 if ( ! $this->is_supported() ) {
695 return;
696 }
697
698 $wp_customize->remove_panel( 'themes' );
699 }
700
701 /**
702 * Add data to be available in JS.
703 *
704 * @since 2.7.0
705 *
706 * @param $localized
707 *
708 * @return mixed
709 */
710 public function add_to_localized_data( $localized ) {
711 if ( empty( $localized['styleManager'] ) ) {
712 $localized['styleManager'] = array();
713 }
714
715 $localized['styleManager']['userFeedback'] = array(
716 'nonce' => wp_create_nonce( 'customify_style_manager_user_feedback' ),
717 'provided' => get_option( 'style_manager_user_feedback_provided', false ),
718 );
719
720 return $localized;
721 }
722
723 /**
724 * Output the user feedback modal markup, if we need to.
725 *
726 * @since 1.7.0
727 */
728 public function output_user_feedback_modal() {
729 // If there is no style manager support, bail early.
730 if ( ! $this->is_supported() ) {
731 return;
732 }
733
734 // We want to ask for feedback once a month.
735 $a_month_back = time() - MONTH_IN_SECONDS;
736
737 // Only output if we should ask for feedback.
738 if ( $this->should_ask_for_feedback( $a_month_back ) ) { ?>
739 <div id="style-manager-user-feedback-modal">
740 <div class="modal">
741 <div class="modal-dialog" role="document">
742 <div class="modal-content">
743 <form id="style-manager-user-feedback" action="#" method="post">
744 <input type="hidden" name="type" value="1_to_5"/>
745 <div class="modal-header">
746 <button type="button" class="close icon media-modal-close" data-dismiss="modal"
747 aria-label="Close"><span class="media-modal-icon"><span
748 class="screen-reader-text">Close media panel</span></span></button>
749 <!-- <a href="#" class="close button button--naked gray" data-dismiss="modal" aria-label="Close">Close</a> -->
750 </div>
751 <div class="modal-body full">
752 <div class="box box--large">
753 <div class="first-step">
754 <h2 class="modal-title">How would you rate your experience in finding the right colors for your site?</h2>
755 <div class="scorecard">
756 <span>Poor</span>
757 <label>
758 <input type="radio" name="rating" value="1" required/>
759 <span>1</span>
760 </label>
761 <label>
762 <input type="radio" name="rating" value="2" required/>
763 <span>2</span>
764 </label>
765 <label>
766 <input type="radio" name="rating" value="3" required/>
767 <span>3</span>
768 </label>
769 <label>
770 <input type="radio" name="rating" value="4" required/>
771 <span>4</span>
772 </label>
773 <label>
774 <input type="radio" name="rating" value="5" required/>
775 <span>5</span>
776 </label>
777 <span>Great</span>
778 </div>
779 </div>
780 <div class="second-step hidden">
781 <p><strong>What points along the way made this a <span
782 class="rating-placeholder">5</span>* experience for
783 you?</strong><br>We are counting on your insights to guide us in
784 doing better 🙏</p>
785 <div class="not-floating-labels">
786 <div class="form-row field">
787 <textarea name="message"
788 placeholder="Describe your experience in customizing your site colors.."
789 id="style-manager-user-feedback-message" rows="6"
790 oninvalid="this.setCustomValidity('May we have a little more info about your experience?')"
791 oninput="setCustomValidity('')" required></textarea>
792 </div>
793 </div>
794 <button id="style-manager-user-feedback_btn" class="button"
795 type="submit"><?php _e( 'Send us your insights', 'customify' ); ?></button>
796 </div>
797 <div class="thanks-step hidden">
798 <h3 class="modal-title">Thank you so much for your feedback!</h3>
799 <p>It means the world to us as we strive to constantly push the limits
800 and aim higher. Stay awesome! 🤗</p>
801 <p><em>The Pixelgrade Team</em></p>
802 </div>
803 <div class="error-step hidden">
804 <h3 class="modal-title">We've hit a snag!</h3>
805 <p>We couldn't record your feedback and we would truly appreciate it if
806 you would try it again at a latter time. Stay awesome! 🤗</p>
807 </div>
808 </div>
809 </div>
810 <div class="modal-footer full">
811
812 </div>
813 </form>
814 </div>
815 </div>
816 </div>
817 <!-- End Modal -->
818 <!-- Modal Backdrop (Shadow) -->
819 <div class="modal-backdrop"></div>
820 </div>
821
822 <?php }
823 }
824
825 /**
826 * Return whether user provided feedback, and if so, return the timestamp.
827 *
828 * @return bool|int
829 */
830 public function user_provided_feedback() {
831 $user_provided_feedback = get_option( 'style_manager_user_feedback_provided' );
832 if ( empty( $user_provided_feedback ) ) {
833 return false;
834 }
835
836 return $user_provided_feedback;
837 }
838
839 /**
840 * Determine if we should ask for user feedback.
841 *
842 * @param bool|int $timestamp_limit Optional. Timestamp to compare the time the user provided feedback.
843 * If the provided timestamp is earlier than the time the user provided feedback, should ask again.
844 *
845 * @return bool
846 */
847 public function should_ask_for_feedback( $timestamp_limit = false ) {
848 if ( defined( 'CUSTOMIFY_SM_ALWAYS_ASK_FOR_FEEDBACK' ) && true === CUSTOMIFY_SM_ALWAYS_ASK_FOR_FEEDBACK ) {
849 return true;
850 }
851
852 $feedback_timestamp = $this->user_provided_feedback();
853 if ( empty( $feedback_timestamp ) ) {
854 return true;
855 }
856
857 if ( ! empty( $timestamp_limit ) && intval( $timestamp_limit ) > intval( $feedback_timestamp ) ) {
858 return true;
859 }
860
861 return false;
862 }
863
864 /**
865 * Callback for the user feedback AJAX call.
866 *
867 * @since 1.7.0
868 */
869 public function user_feedback_callback() {
870 check_ajax_referer( 'customify_style_manager_user_feedback', 'nonce' );
871
872 if ( ! current_user_can( 'manage_options' ) ) {
873 wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'customify' ) );
874 }
875
876 if ( empty( $_POST['type'] ) ) {
877 wp_send_json_error( esc_html__( 'No type provided', 'customify' ) );
878 }
879
880 if ( empty( $_POST['rating'] ) ) {
881 wp_send_json_error( esc_html__( 'No rating provided', 'customify' ) );
882 }
883
884 $type = sanitize_text_field( $_POST['type'] );
885 $rating = intval( $_POST['rating'] );
886 $message = '';
887 if ( ! empty( $_POST['message'] ) ) {
888 $message = wp_kses_post( $_POST['message'] );
889 }
890
891 $request_data = array(
892 'site_url' => home_url( '/' ),
893 'satisfaction_data' => array(
894 'type' => $type,
895 'rating' => $rating,
896 'message' => $message,
897 ),
898 );
899
900 // Send the feedback.
901 $response = $this->cloud_api->send_stats( $request_data, true );
902 if ( is_wp_error( $response ) ) {
903 wp_send_json_error( esc_html__( 'Sorry, something went wrong and we couldn\'t save your feedback.', 'customify' ) );
904 }
905 $response_data = json_decode( wp_remote_retrieve_body( $response ), true );
906 // Bail in case of decode error or failure to retrieve data
907 if ( null === $response_data || empty( $response_data['code'] ) || 'success' !== $response_data['code'] ) {
908 wp_send_json_error( esc_html__( 'Sorry, something went wrong and we couldn\'t save your feedback.', 'customify' ) );
909 }
910
911 // We need to remember that the user provided feedback (and at what timestamp).
912 update_option( 'style_manager_user_feedback_provided', time(), true );
913
914 wp_send_json_success( esc_html__( 'Thank you for your feedback.', 'customify' ) );
915 }
916
917 /**
918 * Main Customify_Style_Manager Instance
919 *
920 * Ensures only one instance of Customify_Style_Manager is loaded or can be loaded.
921 *
922 * @return Customify_Style_Manager Main Customify_Style_Manager instance
923 * @since 1.7.0
924 * @static
925 *
926 */
927 public static function instance() {
928
929 if ( is_null( self::$_instance ) ) {
930 self::$_instance = new self();
931 }
932
933 return self::$_instance;
934 }
935
936 /**
937 * Cloning is forbidden.
938 *
939 * @since 1.7.0
940 */
941 public function __clone() {
942
943 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
944 }
945
946 /**
947 * Unserializing instances of this class is forbidden.
948 *
949 * @since 1.7.0
950 */
951 public function __wakeup() {
952
953 _doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), null );
954 }
955 }
956 }
957