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

668 lines 20.9 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 notifications object.
60 * @var null|Pixcloud_Admin_Notifications_Manager
61 * @access public
62 * @since 1.9.0
63 */
64 protected $notifications = 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 * Constructor.
76 *
77 * @since 1.7.0
78 */
79 protected function __construct() {
80 $this->init();
81 }
82
83 /**
84 * Initialize this module.
85 *
86 * @since 1.7.4
87 */
88 public function init() {
89 /**
90 * Initialize the Themes Config logic.
91 */
92 require_once 'class-customify-theme-configs.php';
93 $this->theme_configs = Customify_Theme_Configs::instance();
94
95 /**
96 * Initialize the Color Palettes logic.
97 */
98 require_once 'class-customify-color-palettes.php';
99 $this->color_palettes = Customify_Color_Palettes::instance();
100
101 // /**
102 // * Initialize the Font Palettes logic.
103 // */
104 // require_once 'class-customify-font-palettes.php';
105 // $this->font_palettes = Customify_Font_Palettes::instance();
106
107 /**
108 * Initialize the Notifications logic.
109 */
110 require_once 'admin-notifications-manager/class-admin-notifications-manager.php';
111 $this->notifications = Pixcloud_Admin_Notifications_Manager::instance(
112 array(
113 'plugin_name' => 'Customify',
114 'text_domain' => 'customify',
115 'version' => '',
116 )
117 );
118
119 /**
120 * Initialize the Cloud API logic.
121 */
122 require_once 'lib/class-customify-cloud-api.php';
123 $this->cloud_api = new Customify_Cloud_Api();
124
125 // Hook up.
126 $this->add_hooks();
127 }
128
129 /**
130 * Initiate our hooks
131 *
132 * @since 1.7.0
133 */
134 public function add_hooks() {
135 /*
136 * Handle the Customizer Style Manager base config.
137 */
138 add_filter( 'customify_filter_fields', array( $this, 'style_manager_section_base_config' ), 12, 1 );
139
140 /*
141 * Handle the grouping and reorganization of the Customizer theme sections when the Style Manager is active.
142 */
143 add_filter( 'customify_final_config', array( $this, 'reorganize_sections' ), 10, 1 );
144
145 /*
146 * Handle the logic for user feedback.
147 */
148 add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_user_feedback_modal' ) );
149 add_action( 'wp_ajax_customify_style_manager_user_feedback', array( $this, 'user_feedback_callback' ) );
150
151 /*
152 * Scripts enqueued in the Customizer.
153 */
154 add_action( 'customize_controls_init', array( $this, 'register_admin_customizer_scripts' ), 10 );
155 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_admin_customizer_scripts' ), 10 );
156 }
157
158 /**
159 * Register Customizer admin scripts.
160 */
161 function register_admin_customizer_scripts() {
162 wp_register_script( PixCustomifyPlugin()->get_slug() . '-style-manager', plugins_url( 'js/customizer/style-manager.js', PixCustomifyPlugin()->get_file() ), array( 'jquery' ), PixCustomifyPlugin()->get_version() );
163 }
164
165 /**
166 * Enqueue Customizer admin scripts
167 */
168 function enqueue_admin_customizer_scripts() {
169 // If there is no style manager support, bail early.
170 if ( ! $this->is_supported() ) {
171 return;
172 }
173
174 // Enqueue the needed scripts, already registered.
175 wp_enqueue_script( PixCustomifyPlugin()->get_slug() . '-style-manager' );
176 }
177
178 /**
179 * Determine if Style Manager is supported.
180 *
181 * @since 1.7.0
182 *
183 * @return bool
184 */
185 public function is_supported() {
186 $has_support = (bool) current_theme_supports( 'customizer_style_manager' );
187
188 return apply_filters( 'customify_style_manager_is_supported', $has_support );
189 }
190
191 /**
192 * Setup the Style Manager Customizer section base config.
193 *
194 * This handles the base configuration for the controls in the Style Manager section. We expect other parties (e.g. the theme),
195 * to come and fill up the missing details (e.g. connected fields).
196 *
197 * @since 1.7.0
198 *
199 * @param array $config This holds required keys for the plugin config like 'opt-name', 'panels', 'settings'
200 * @return array
201 */
202 public function style_manager_section_base_config( $config ) {
203 // If there is no style manager support, bail early.
204 if ( ! $this->is_supported() ) {
205 return $config;
206 }
207
208 if ( ! isset( $config['sections']['style_manager_section'] ) ) {
209 $config['sections']['style_manager_section'] = array();
210 }
211
212 // The section might be already defined, thus we merge, not replace the entire section config.
213 $config['sections']['style_manager_section'] = array_replace_recursive( $config['sections']['style_manager_section'], array(
214 'title' => esc_html__( 'Style Manager', 'customify' ),
215 'section_id' => 'style_manager_section', // We will force this section id preventing prefixing and other regular processing.
216 'priority' => 1,
217 'options' => array(),
218 ) );
219
220 return $config;
221 }
222
223 /**
224 * Reorganize the Customizer sections.
225 *
226 * @since 1.7.4
227 *
228 * @param array $config This holds required keys for the plugin config like 'opt-name', 'panels', 'settings'.
229 * @return array
230 */
231 public function reorganize_sections( $config ) {
232 // If there is no style manager support, bail early.
233 if ( ! $this->is_supported() ) {
234 return $config;
235 }
236
237 // If there is no Style Manager section, bail.
238 if ( ! isset( $config['sections']['style_manager_section'] ) ) {
239 return $config;
240 }
241
242 $style_manager_section_config = $config['sections']['style_manager_section'];
243 unset( $config['sections']['style_manager_section'] );
244 // All the other sections.
245 $other_theme_sections_config = $config['sections'];
246 unset( $config['sections'] );
247
248 // Now group them in panels.
249 if ( ! isset( $config['panels'] ) ) {
250 $config['panels'] = array();
251 }
252
253 // The Style Manager panel.
254 $config['panels']['style_manager_panel'] = array(
255 'priority' => 22,
256 'capability' => 'edit_theme_options',
257 'panel_id' => 'style_manager_panel',
258 'title' => esc_html__( 'Style Manager', 'customify' ),
259 'description' => __( '<strong>Style Manager</strong> is an intuitive system to help you change the look of your website and make an excellent impression.', 'customify' ),
260 'sections' => array(),
261 'auto_expand_sole_section' => true, // If there is only one section in the panel, auto-expand it.
262 );
263
264 // Maybe handle the color palettes.
265 if ( class_exists( 'Customify_Color_Palettes' ) && Customify_Color_Palettes::instance()->is_supported() ) {
266
267 // We need to split the fields in the Style Manager section into two: color palettes and fonts.
268 $color_palettes_fields = array(
269 'sm_current_color_palette',
270 'sm_palettes_description',
271 'sm_filters_description',
272 'sm_customize_description',
273 'sm_color_matrix',
274 'sm_palette_filter',
275 'sm_coloration_level',
276 'sm_color_diversity',
277 'sm_shuffle_colors',
278 'sm_dark_mode',
279 'sm_dark_color_master_slider',
280 'sm_dark_color_primary_slider',
281 'sm_dark_color_secondary_slider',
282 'sm_dark_color_tertiary_slider',
283 'sm_colors_dispersion',
284 'sm_colors_focus_point',
285 'sm_color_palette',
286 'sm_color_palette_variation',
287 'sm_color_primary',
288 'sm_color_primary_connected',
289 'sm_color_secondary',
290 'sm_color_secondary_connected',
291 'sm_color_tertiary',
292 'sm_color_tertiary_connected',
293 'sm_dark_primary',
294 'sm_dark_primary_connected',
295 'sm_dark_secondary',
296 'sm_dark_secondary_connected',
297 'sm_dark_tertiary',
298 'sm_dark_tertiary_connected',
299 'sm_light_primary',
300 'sm_light_primary_connected',
301 'sm_light_secondary',
302 'sm_light_secondary_connected',
303 'sm_light_tertiary',
304 'sm_light_tertiary_connected',
305 'sm_swap_colors',
306 'sm_swap_dark_light',
307 'sm_swap_colors_dark',
308 'sm_swap_secondary_colors_dark',
309 'sm_advanced_toggle',
310 'sm_spacing_bottom',
311 );
312
313 $color_palettes_section_config = array(
314 'title' => esc_html__( 'Colors', 'customify' ),
315 'section_id' => 'sm_color_palettes_section',
316 'priority' => 10,
317 'options' => array(),
318 );
319 foreach ( $color_palettes_fields as $field_id ) {
320 if ( ! isset( $style_manager_section_config['options'][ $field_id ] ) ) {
321 continue;
322 }
323
324 if ( empty( $color_palettes_section_config['options'] ) ) {
325 $color_palettes_section_config['options'] = array( $field_id => $style_manager_section_config['options'][ $field_id ] );
326 } else {
327 $color_palettes_section_config['options'] = array_merge( $color_palettes_section_config['options'], array( $field_id => $style_manager_section_config['options'][ $field_id ] ) );
328 }
329 }
330
331 $config['panels']['style_manager_panel']['sections']['sm_color_palettes_section'] = $color_palettes_section_config;
332 }
333
334 // Maybe handle the font palettes.
335 if ( class_exists( 'Customify_Font_Palettes' ) && Customify_Font_Palettes::instance()->is_supported() ) {
336
337 $font_palettes_fields = array(
338 'sm_font_palette',
339 'sm_font_palette_variation',
340 'sm_font_primary',
341 'sm_font_secondary',
342 'sm_font_body',
343 'sm_swap_fonts',
344 'sm_swap_primary_secondary_fonts',
345 );
346
347 $font_palettes_section_config = array(
348 'title' => esc_html__( 'Fonts', 'customify' ),
349 'section_id' => 'sm_font_palettes_section',
350 'priority' => 20,
351 'options' => array(),
352 );
353 foreach ( $font_palettes_fields as $field_id ) {
354 if ( ! isset( $style_manager_section_config['options'][ $field_id ] ) ) {
355 continue;
356 }
357
358 if ( empty( $font_palettes_section_config['options'] ) ) {
359 $font_palettes_section_config['options'] = array( $field_id => $style_manager_section_config['options'][ $field_id ] );
360 } else {
361 $font_palettes_section_config['options'] = array_merge( $font_palettes_section_config['options'], array( $field_id => $style_manager_section_config['options'][ $field_id ] ) );
362 }
363 }
364
365 $config['panels']['style_manager_panel']['sections']['sm_font_palettes_section'] = $font_palettes_section_config;
366 }
367
368 // The Theme Options panel.
369 $config['panels']['theme_options_panel'] = array(
370 'priority' => 23,
371 'capability' => 'edit_theme_options',
372 'panel_id' => 'theme_options_panel',
373 'title' => esc_html__( 'Theme Options', 'customify' ),
374 'description' => esc_html__( 'Advanced options to change your site look-and-feel on a detailed level.', 'customify' ),
375 'sections' => $other_theme_sections_config,
376 );
377
378 // Add the logic that handles sections and controls added directly to WP_Customizer, not through the config.
379 add_action( 'customize_register', array( $this, 'reorganize_direct_sections_and_controls' ), 100 );
380
381 // Remove the switch theme panel from the Customizer.
382 add_action( 'customize_register', array( $this, 'remove_switch_theme_panel' ), 12 );
383
384 return $config;
385 }
386
387 /**
388 * Reorganizes sections and controls added directly to WP_Customizer, not through the config.
389 *
390 * @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!
391 *
392 * @since 1.9.0
393 *
394 * @param WP_Customize_Manager $wp_customize
395 */
396 public function reorganize_direct_sections_and_controls( $wp_customize ) {
397 // We will do out best to identify direct sections and move their controls to the appropriate place.
398 /** @var WP_Customize_Section $section */
399 foreach ( $wp_customize->sections() as $section ) {
400 // These are general theme options sections that need to have their controls moved to the Theme Options > General section.
401 if ( false !== strpos( $section->id, 'theme_options') ) {
402 $theme_options_panel = $wp_customize->get_panel( 'theme_options_panel' );
403 $general_section = false;
404 foreach ( $theme_options_panel->sections as $theme_options_section ) {
405 if ( false !== strpos( $theme_options_section->id, 'general' ) ) {
406 $general_section = $section;
407 }
408 }
409
410 if ( false === $general_section ) {
411 // We need to add a general section in the Theme Options panel.
412 $general_section = $wp_customize->add_section( 'theme_options[general]', array(
413 'title' => esc_html__( 'General', 'customify' ),
414 'panel' => $theme_options_panel->id,
415 'priority' => 2,
416 ) );
417 }
418
419 // Move all the controls in the identified theme options section to the general one.
420 /** @var WP_Customize_Control $control */
421 foreach ( $wp_customize->controls() as $control ) {
422 if ( $control->section !== $section->id ) {
423 continue;
424 }
425
426 $control->section = $general_section->id;
427 }
428
429 // Finally remove the now empty section.
430 $wp_customize->remove_section( $section->id );
431
432 break;
433 }
434 }
435 }
436
437 /**
438 * Remove the switch/preview theme panel.
439 *
440 * @since 1.7.4
441 *
442 * @param WP_Customize_Manager $wp_customize
443 */
444 public function remove_switch_theme_panel( $wp_customize ) {
445 $wp_customize->remove_panel( 'themes' );
446 }
447
448 /**
449 * Output the user feedback modal markup, if we need to.
450 *
451 * @since 1.7.0
452 */
453 public function output_user_feedback_modal() {
454 // If there is no style manager support, bail early.
455 if ( ! $this->is_supported() ) {
456 return;
457 }
458
459 // We want to ask for feedback once a month.
460 $a_month_back = time() - MONTH_IN_SECONDS;
461
462 // Only output if we should ask for feedback.
463 if ( $this->should_ask_for_feedback( $a_month_back ) ) { ?>
464 <div id="style-manager-user-feedback-modal">
465 <div class="modal">
466 <div class="modal-dialog" role="document">
467 <div class="modal-content">
468 <form id="style-manager-user-feedback" action="#" method="post">
469 <input type="hidden" name="type" value="1_to_5" />
470 <div class="modal-header">
471 <button type="button" class="close icon media-modal-close" data-dismiss="modal" aria-label="Close"><span class="media-modal-icon"><span class="screen-reader-text">Close media panel</span></span></button>
472 <!-- <a href="#" class="close button button--naked gray" data-dismiss="modal" aria-label="Close">Close</a> -->
473 </div>
474 <div class="modal-body full">
475 <div class="box box--large">
476 <div class="first-step">
477 <h2 class="modal-title">How would you rate your experience in finding the right colors for your site?</h2>
478 <div class="scorecard">
479 <span>Poor</span>
480 <label>
481 <input type="radio" name="rating" value="1" required />
482 <span>1</span>
483 </label>
484 <label>
485 <input type="radio" name="rating" value="2" required />
486 <span>2</span>
487 </label>
488 <label>
489 <input type="radio" name="rating" value="3" required />
490 <span>3</span>
491 </label>
492 <label>
493 <input type="radio" name="rating" value="4" required />
494 <span>4</span>
495 </label>
496 <label>
497 <input type="radio" name="rating" value="5" required />
498 <span>5</span>
499 </label>
500 <span>Great</span>
501 </div>
502 </div>
503 <div class="second-step hidden">
504 <p><strong>What points along the way made this a <span class="rating-placeholder">5</span>* experience for you?</strong><br>We are counting on your insights to guide us in doing better 🙏</p>
505 <div class="not-floating-labels">
506 <div class="form-row field">
507 <textarea name="message" placeholder="Describe your experience in customizing your site colors.."
508 id="style-manager-user-feedback-message" rows="6" oninvalid="this.setCustomValidity('May we have a little more info about your experience?')" oninput="setCustomValidity('')" required></textarea>
509 </div>
510 </div>
511 <button id="style-manager-user-feedback_btn" class="button" type="submit"><?php _e( 'Send us your insights', 'customify' ); ?></button>
512 </div>
513 <div class="thanks-step hidden">
514 <h3 class="modal-title">Thank you so much for your feedback!</h3>
515 <p>It means the world to us as we strive to constantly push the limits and aim higher. Stay awesome! 🤗</p>
516 <p><em>The Pixelgrade Team</em></p>
517 </div>
518 <div class="error-step hidden">
519 <h3 class="modal-title">We've hit a snag!</h3>
520 <p>We couldn't record your feedback and we would truly appreciate it if you would try it again at a latter time. Stay awesome! 🤗</p>
521 </div>
522 </div>
523 </div>
524 <div class="modal-footer full">
525
526 </div>
527 </form>
528 </div>
529 </div>
530 </div>
531 <!-- End Modal -->
532 <!-- Modal Backdrop (Shadow) -->
533 <div class="modal-backdrop"></div>
534 </div>
535
536 <?php }
537 }
538
539 /**
540 * Return whether user provided feedback, and if so, return the timestamp.
541 *
542 * @return bool|int
543 */
544 public function user_provided_feedback() {
545 $user_provided_feedback = get_option( 'style_manager_user_feedback_provided' );
546 if ( empty( $user_provided_feedback ) ) {
547 return false;
548 }
549
550 return $user_provided_feedback;
551 }
552
553 /**
554 * Determine if we should ask for user feedback.
555 *
556 * @param bool|int $timestamp_limit Optional. Timestamp to compare the time the user provided feedback.
557 * If the provided timestamp is earlier than the time the user provided feedback, should ask again.
558 *
559 * @return bool
560 */
561 public function should_ask_for_feedback( $timestamp_limit = false ) {
562 if ( defined( 'CUSTOMIFY_SM_ALWAYS_ASK_FOR_FEEDBACK' ) && true === CUSTOMIFY_SM_ALWAYS_ASK_FOR_FEEDBACK ) {
563 return true;
564 }
565
566 $feedback_timestamp = $this->user_provided_feedback();
567 if ( empty( $feedback_timestamp ) ) {
568 return true;
569 }
570
571 if ( ! empty( $timestamp_limit ) && intval( $timestamp_limit ) > intval( $feedback_timestamp ) ) {
572 return true;
573 }
574
575 return false;
576 }
577
578 /**
579 * Callback for the user feedback AJAX call.
580 *
581 * @since 1.7.0
582 */
583 public function user_feedback_callback() {
584 check_ajax_referer( 'customify_style_manager_user_feedback', 'nonce' );
585
586 if ( empty( $_POST['type'] ) ) {
587 wp_send_json_error( esc_html__( 'No type provided', 'customify' ) );
588 }
589
590 if ( empty( $_POST['rating'] ) ) {
591 wp_send_json_error( esc_html__( 'No rating provided', 'customify' ) );
592 }
593
594 $type = sanitize_text_field( $_POST['type'] );
595 $rating = intval( $_POST['rating'] );
596 $message = '';
597 if ( ! empty( $_POST['message'] ) ) {
598 $message = wp_kses_post( $_POST['message'] );
599 }
600
601 $request_data = array(
602 'site_url' => home_url( '/' ),
603 'satisfaction_data' => array(
604 'type' => $type,
605 'rating' => $rating,
606 'message' => $message,
607 ),
608 );
609
610 // Send the feedback.
611 $response = $this->cloud_api->send_stats( $request_data, true );
612 if ( is_wp_error( $response ) ) {
613 wp_send_json_error( esc_html__( 'Sorry, something went wrong and we couldn\'t save your feedback.', 'customify' ) );
614 }
615 $response_data = json_decode( wp_remote_retrieve_body( $response ), true );
616 // Bail in case of decode error or failure to retrieve data
617 if ( null === $response_data || empty( $response_data['code'] ) || 'success' !== $response_data['code'] ) {
618 wp_send_json_error( esc_html__( 'Sorry, something went wrong and we couldn\'t save your feedback.', 'customify' ) );
619 }
620
621 // We need to remember that the user provided feedback (and at what timestamp).
622 update_option( 'style_manager_user_feedback_provided', time(), true );
623
624 wp_send_json_success( esc_html__( 'Thank you for your feedback.', 'customify' ) );
625 }
626
627 /**
628 * Main Customify_Style_Manager Instance
629 *
630 * Ensures only one instance of Customify_Style_Manager is loaded or can be loaded.
631 *
632 * @since 1.7.0
633 * @static
634 *
635 * @return Customify_Style_Manager Main Customify_Style_Manager instance
636 */
637 public static function instance() {
638
639 if ( is_null( self::$_instance ) ) {
640 self::$_instance = new self();
641 }
642
643 return self::$_instance;
644 } // End instance ()
645
646 /**
647 * Cloning is forbidden.
648 *
649 * @since 1.7.0
650 */
651 public function __clone() {
652
653 _doing_it_wrong( __FUNCTION__,esc_html( __( 'Cheatin&#8217; huh?' ) ), null );
654 } // End __clone ()
655
656 /**
657 * Unserializing instances of this class is forbidden.
658 *
659 * @since 1.7.0
660 */
661 public function __wakeup() {
662
663 _doing_it_wrong( __FUNCTION__, esc_html( __( 'Cheatin&#8217; huh?' ) ), null );
664 } // End __wakeup ()
665 }
666
667 endif;
668