PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.8
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.8
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
← All changes | classes/Visualizer/Module/Admin.php +558 -96 3.10.14.0.8 View file →
@@ -29,8 +29,10 @@
29 29 class Visualizer_Module_Admin extends Visualizer_Module {
30 30
31 31 const NAME = __CLASS__;
32 32
33 + const OPTION_GLOBAL_SETTINGS = 'visualizer_global_settings';
34 +
33 35 /**
34 36 * Library page suffix.
35 37 *
36 38 * @since 1.0.0
@@ -40,8 +42,24 @@
40 42 */
41 43 private $_libraryPage;
42 44
43 45 /**
46 + * Support page suffix.
47 + *
48 + * @access private
49 + * @var string
50 + */
51 + private $_supportPage;
52 +
53 + /**
54 + * Settings page suffix.
55 + *
56 + * @access private
57 + * @var string
58 + */
59 + private $_settingsPage;
60 +
61 + /**
44 62 * Constructor.
45 63 *
46 64 * @since 1.0.0
47 65 *
@@ -52,16 +70,20 @@
52 70 public function __construct( Visualizer_Plugin $plugin ) {
53 71 parent::__construct( $plugin );
54 72 $this->_addAction( 'load-post.php', 'enqueueMediaScripts' );
55 73 $this->_addAction( 'load-post-new.php', 'enqueueMediaScripts' );
74 + $this->_addAction( 'enqueue_block_editor_assets', 'enqueueMediaScripts' );
56 75 $this->_addAction( 'admin_footer', 'renderTemplates' );
57 76 $this->_addAction( 'admin_enqueue_scripts', 'enqueueLibraryScripts', null, 0 );
77 + $this->_addAction( 'admin_enqueue_scripts', 'enqueue_support_page' );
78 + $this->_addAction( 'admin_enqueue_scripts', 'enqueueSettingsScripts' );
79 + $this->_addAction( 'admin_post_visualizer_save_global_settings', 'saveGlobalSettings' );
58 80 $this->_addAction( 'admin_menu', 'registerAdminMenu' );
59 81 $this->_addFilter( 'media_view_strings', 'setupMediaViewStrings' );
60 82 $this->_addFilter( 'plugin_action_links', 'getPluginActionLinks', 10, 2 );
61 83 $this->_addFilter( 'plugin_row_meta', 'getPluginMetaLinks', 10, 2 );
62 - $this->_addFilter( 'visualizer_logger_data', 'getLoggerData' );
63 84 $this->_addFilter( 'visualizer_feedback_review_trigger', 'feedbackReviewTrigger' );
85 + $this->_addFilter( 'themeisle_sdk_blackfriday_data', 'add_black_friday_data' );
64 86
65 87 // screen pagination
66 88 $this->_addFilter( 'set-screen-option', 'setScreenOptions', 10, 3 );
67 89
@@ -75,14 +97,45 @@
75 97 $this->_addAction( 'admin_init', 'init' );
76 98
77 99 $this->_addAction( 'visualizer_chart_languages', 'addMultilingualSupport' );
78 100
79 - if ( defined( 'TI_CYPRESS_TESTING' ) ) {
101 + $this->_addFilter( 'admin_footer_text', 'render_review_notice' );
102 +
103 + if ( ! defined( 'TI_E2E_TESTING' ) ) {
104 + $this->_addFilter( 'themeisle-sdk/survey/' . VISUALIZER_DIRNAME, 'get_survey_metadata', 10, 2 );
105 + }
106 +
107 + if ( defined( 'TI_E2E_TESTING' ) ) {
80 108 $this->load_cypress_hooks();
81 109 }
110 + }
111 + /**
112 + * Display review notice.
113 + */
114 + public function render_review_notice( $footer_text ) {
115 + $current_screen = get_current_screen();
82 116
117 + $visualizer_page_ids = array( 'toplevel_page_visualizer', 'visualizer_page_viz-support', 'visualizer_page_ti-about-visualizer' );
118 +
119 + if ( ! empty( $current_screen ) && isset( $current_screen->id ) ) {
120 + foreach ( $visualizer_page_ids as $page_to_check ) {
121 + if ( strpos( $current_screen->id, $page_to_check ) !== false ) {
122 + $footer_text = sprintf(
123 + // translators: %1$s - the name of the plugin (Visualizer), %2$s - message (You can help us by leaving a), %3$s - HTML entity code.
124 + __( 'Enjoying %1$s? %2$s %3$s rating. Thank you for being so supportive!', 'visualizer' ),
125 + '<b>Visualizer</b>',
126 + esc_html__( 'You can help us by leaving a', 'visualizer' ),
127 + '<a href="https://wordpress.org/support/plugin/visualizer/reviews/#new-post" target="_blank">&#9733;&#9733;&#9733;&#9733;&#9733;</a>'
128 + );
129 + break;
130 + }
131 + }
132 + }
133 +
134 + return $footer_text;
83 135 }
84 136
137 +
85 138 /**
86 139 * Define the hooks that are needed for cypress.
87 140 *
88 141 * @since 3.4.0
@@ -90,9 +143,9 @@
90 143 */
91 144 private function load_cypress_hooks() {
92 145 // all charts should load on the same page without pagination.
93 146 add_filter(
94 - 'visualizer_query_args', function( $args ) {
147 + 'visualizer_query_args', function ( $args ) {
95 148 $args['posts_per_page'] = 20;
96 149 return $args;
97 150 }, 10, 1
98 151 );
@@ -113,23 +166,16 @@
113 166 if ( apply_filters( 'visualizer_is_business', false ) ) {
114 167 return;
115 168 }
116 169
117 - $license = __( 'PRO', 'visualizer' );
118 - if ( Visualizer_Module::is_pro() ) {
119 - switch ( $plan ) {
120 - case 1:
121 - $license = __( 'Developer', 'visualizer' );
122 - break;
123 - }
124 - }
170 + $is_new_personal = apply_filters( 'visualizer_is_new_personal', false );
125 171
126 172 $hours = array(
127 - '0' => __( 'Live', 'visualizer' ),
128 - '1' => __( 'Each hour', 'visualizer' ),
129 - '12' => __( 'Each 12 hours', 'visualizer' ),
130 - '24' => __( 'Each day', 'visualizer' ),
131 - '72' => __( 'Each 3 days', 'visualizer' ),
173 + '0.16' => __( '10 minutes', 'visualizer' ),
174 + '1' => __( 'Each hour', 'visualizer' ),
175 + '12' => __( 'Each 12 hours', 'visualizer' ),
176 + '24' => __( 'Each day', 'visualizer' ),
177 + '72' => __( 'Each 3 days', 'visualizer' ),
132 178 );
133 179
134 180 switch ( $feature ) {
135 181 case 'json':
@@ -134,9 +180,9 @@
134 180 switch ( $feature ) {
135 181 case 'json':
136 182 case 'csv':
137 183 // no more schedules if pro is already active.
138 - if ( Visualizer_Module::is_pro() ) {
184 + if ( Visualizer_Module::is_pro() && ! $is_new_personal ) {
139 185 return;
140 186 }
141 187 break;
142 188 case 'wp':
@@ -146,9 +192,9 @@
146 192 default:
147 193 return;
148 194 }
149 195
150 - echo '<optgroup disabled label="' . sprintf( __( 'More in the %s version', 'visualizer' ), $license ) . '">';
196 + echo '<optgroup disabled label="' . __( 'Upgrade required', 'visualizer' ) . '">';
151 197 foreach ( $hours as $hour => $desc ) {
152 198 echo '<option disabled>' . $desc . '</option>';
153 199 }
154 200 echo '</optgroup>';
@@ -175,9 +221,9 @@
175 221
176 222 if ( $meta ) {
177 223 foreach ( $meta as $key => $value ) {
178 224 if ( 0 === strpos( $key, 'visualizer' ) ) {
179 - add_metadata( 'post', $revision_id, $key, maybe_unserialize( $value[0] ) );
225 + add_metadata( 'post', $revision_id, $key, self::maybe_decode_content( $value[0] ) );
180 226 }
181 227 }
182 228 }
183 229 }
@@ -203,9 +249,9 @@
203 249
204 250 if ( $meta ) {
205 251 foreach ( $meta as $key => $value ) {
206 252 if ( 0 === strpos( $key, 'visualizer' ) ) {
207 - add_post_meta( $post_id, $key, maybe_unserialize( $value[0] ) );
253 + add_post_meta( $post_id, $key, self::maybe_decode_content( $value[0] ) );
208 254 }
209 255 }
210 256 }
211 257 }
@@ -216,10 +262,9 @@
216 262 *
217 263 * @access public
218 264 */
219 265 public function init() {
220 - // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
221 - if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' == get_user_option( 'rich_editing' ) ) {
266 + if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' === get_user_option( 'rich_editing' ) ) {
222 267 $this->_addFilter( 'mce_external_languages', 'add_tinymce_lang', 10, 1 );
223 268 $this->_addFilter( 'mce_external_plugins', 'tinymce_plugin', 10, 1 );
224 269 $this->_addFilter( 'mce_buttons', 'register_mce_button', 10, 1 );
225 270 $this->_addFilter( 'tiny_mce_before_init', 'get_strings_for_block', 10, 1 );
@@ -231,9 +276,9 @@
231 276 *
232 277 * @since ?
233 278 * @access friendly
234 279 */
235 - function get_strings_for_block( $settings ) {
280 + public function get_strings_for_block( $settings ) {
236 281 $class = new Visualizer_Module_Language();
237 282 $strings = $class->get_strings();
238 283 $array = array( 'visualizer_tinymce_plugin' => json_encode( $strings ) );
239 284 return array_merge( $settings, $array );
@@ -309,11 +354,36 @@
309 354 * @access public
310 355 */
311 356 public function enqueueMediaScripts() {
312 357 global $typenow;
313 - if ( post_type_supports( $typenow, 'editor' ) ) {
358 + global $current_screen;
359 +
360 + if ( post_type_supports( $typenow, 'editor' ) || $current_screen->id === 'widgets' || $current_screen->id === 'customize' ) {
314 361 wp_enqueue_style( 'visualizer-media', VISUALIZER_ABSURL . 'css/media.css', array( 'media-views' ), Visualizer_Plugin::VERSION );
315 362
363 + $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php';
364 + if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
365 + // @phpstan-ignore-next-line
366 + $d3_asset = include $d3_renderer_asset;
367 + wp_register_script(
368 + 'visualizer-d3-renderer',
369 + VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js',
370 + array_merge( $d3_asset['dependencies'], array( 'jquery' ) ),
371 + $d3_asset['version'],
372 + true
373 + );
374 + }
375 + if ( wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
376 + wp_enqueue_script( 'visualizer-d3-renderer' );
377 + wp_localize_script(
378 + 'visualizer-d3-renderer',
379 + 'vizD3Renderer',
380 + array(
381 + 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js',
382 + )
383 + );
384 + }
385 +
316 386 // Load all the assets for the different libraries we support.
317 387 $deps = array(
318 388 Visualizer_Render_Sidebar_Google::enqueue_assets( array( 'media-editor' ) ),
319 389 Visualizer_Render_Sidebar_Type_DataTable_Tabular::enqueue_assets( array( 'media-editor' ) ),
@@ -327,9 +397,9 @@
327 397 'visualizer-media-view',
328 398 'visualizer',
329 399 array(
330 400 'i10n' => array(
331 - 'insert' => __( 'Insert', 'visualizer' ),
401 + 'insert' => __( 'Insert Chart', 'visualizer' ),
332 402 ),
333 403 )
334 404 );
335 405 wp_enqueue_script( 'visualizer-media-toolbar', VISUALIZER_ABSURL . 'js/media/toolbar.js', array( 'visualizer-media-view' ), Visualizer_Plugin::VERSION, true );
@@ -358,9 +428,9 @@
358 428 'controller' => array(
359 429 'title' => esc_html__( 'Visualizations', 'visualizer' ),
360 430 ),
361 431 'routers' => array(
362 - 'library' => esc_html__( 'From Library', 'visualizer' ),
432 + 'library' => esc_html__( 'Insert from Library', 'visualizer' ),
363 433 'create' => esc_html__( 'Create New', 'visualizer' ),
364 434 ),
365 435 'library' => array(
366 436 'filters' => $chart_types,
@@ -365,9 +435,9 @@
365 435 'library' => array(
366 436 'filters' => $chart_types,
367 437 'types' => array_keys( $chart_types ),
368 438 ),
369 - 'nonce' => wp_create_nonce(),
439 + 'nonce' => wp_create_nonce( Visualizer_Plugin::ACTION_GET_CHARTS ),
370 440 'buildurl' => esc_url( add_query_arg( 'action', Visualizer_Plugin::ACTION_CREATE_CHART, admin_url( 'admin-ajax.php' ) ) ),
371 441 );
372 442
373 443 return $strings;
@@ -390,9 +460,9 @@
390 460 'enabled' => true,
391 461 );
392 462 }
393 463
394 - $enabled = self::proFeaturesLocked();
464 + $enabled = self::proFeaturesEnabled();
395 465
396 466 $types = array_merge(
397 467 $additional,
398 468 array(
@@ -403,14 +473,14 @@
403 473 ),
404 474 'pie' => array(
405 475 'name' => esc_html__( 'Pie/Donut', 'visualizer' ),
406 476 'enabled' => true,
407 - 'supports' => $enabled ? array( 'Google Charts', 'ChartJS' ) : array( 'Google Charts' ),
477 + 'supports' => array( 'Google Charts', 'ChartJS' ),
408 478 ),
409 479 'line' => array(
410 480 'name' => esc_html__( 'Line', 'visualizer' ),
411 481 'enabled' => true,
412 - 'supports' => $enabled ? array( 'Google Charts', 'ChartJS' ) : array( 'Google Charts' ),
482 + 'supports' => array( 'Google Charts', 'ChartJS' ),
413 483 ),
414 484 'bar' => array(
415 485 'name' => esc_html__( 'Bar', 'visualizer' ),
416 486 'enabled' => true,
@@ -420,18 +490,21 @@
420 490 'name' => esc_html__( 'Area', 'visualizer' ),
421 491 'enabled' => $enabled,
422 492 // in ChartJS, the fill option is used to make Line chart an area: https://www.chartjs.org/docs/latest/charts/area.html
423 493 'supports' => array( 'Google Charts' ),
494 + 'demo_url' => 'https://demo.themeisle.com/visualizer/area-chart/',
424 495 ),
425 496 'geo' => array(
426 497 'name' => esc_html__( 'Geo', 'visualizer' ),
427 498 'enabled' => $enabled,
428 499 'supports' => array( 'Google Charts' ),
500 + 'demo_url' => 'https://demo.themeisle.com/visualizer/geo-chart/',
429 501 ),
430 502 'column' => array(
431 503 'name' => esc_html__( 'Column', 'visualizer' ),
432 504 'enabled' => $enabled,
433 505 'supports' => array( 'Google Charts', 'ChartJS' ),
506 + 'demo_url' => 'https://demo.themeisle.com/visualizer/column-chart/',
434 507 ),
435 508 'bubble' => array(
436 509 'name' => esc_html__( 'Bubble', 'visualizer' ),
437 510 'enabled' => $enabled,
@@ -436,23 +509,27 @@
436 509 'name' => esc_html__( 'Bubble', 'visualizer' ),
437 510 'enabled' => $enabled,
438 511 // chartjs' bubble is ugly looking (and it won't work off the default bubble.csv) so it is being excluded for the time being.
439 512 'supports' => array( 'Google Charts' ),
513 + 'demo_url' => 'https://demo.themeisle.com/visualizer/bubble-chart/',
440 514 ),
441 515 'scatter' => array(
442 516 'name' => esc_html__( 'Scatter', 'visualizer' ),
443 517 'enabled' => $enabled,
444 518 'supports' => array( 'Google Charts' ),
519 + 'demo_url' => 'https://demo.themeisle.com/visualizer/scatter-chart/',
445 520 ),
446 521 'gauge' => array(
447 522 'name' => esc_html__( 'Gauge', 'visualizer' ),
448 523 'enabled' => $enabled,
449 524 'supports' => array( 'Google Charts' ),
525 + 'demo_url' => 'https://demo.themeisle.com/visualizer/gauge-chart/',
450 526 ),
451 527 'candlestick' => array(
452 528 'name' => esc_html__( 'Candlestick', 'visualizer' ),
453 529 'enabled' => $enabled,
454 530 'supports' => array( 'Google Charts' ),
531 + 'demo_url' => 'https://demo.themeisle.com/visualizer/candlestick-chart/',
455 532 ),
456 533 // pro types
457 534 'timeline' => array(
458 535 'name' => esc_html__( 'Timeline', 'visualizer' ),
@@ -457,23 +534,27 @@
457 534 'timeline' => array(
458 535 'name' => esc_html__( 'Timeline', 'visualizer' ),
459 536 'enabled' => false,
460 537 'supports' => array( 'Google Charts', 'ChartJS' ),
538 + 'demo_url' => 'https://demo.themeisle.com/visualizer/timeline-chart/',
461 539 ),
462 540 'combo' => array(
463 541 'name' => esc_html__( 'Combo', 'visualizer' ),
464 542 'enabled' => false,
465 543 'supports' => array( 'Google Charts' ),
544 + 'demo_url' => 'https://demo.themeisle.com/visualizer/combo-chart/',
466 545 ),
467 546 'polarArea' => array(
468 547 'name' => esc_html__( 'Polar Area', 'visualizer' ),
469 548 'enabled' => false,
470 549 'supports' => array( 'ChartJS' ),
550 + 'demo_url' => 'https://demo.themeisle.com/visualizer/polar-area-chart/',
471 551 ),
472 552 'radar' => array(
473 553 'name' => esc_html__( 'Radar/Spider', 'visualizer' ),
474 554 'enabled' => false,
475 555 'supports' => array( 'ChartJS' ),
556 + 'demo_url' => 'https://demo.themeisle.com/visualizer/radar-spider-chart/',
476 557 ),
477 558 )
478 559 );
479 560 $types = apply_filters( 'visualizer_pro_chart_types', $types );
@@ -600,45 +681,159 @@
600 681 *
601 682 * @param string $suffix The current page suffix.
602 683 */
603 684 public function enqueueLibraryScripts( $suffix ) {
604 - if ( $suffix === $this->_libraryPage ) {
605 - wp_enqueue_style( 'visualizer-library', VISUALIZER_ABSURL . 'css/library.css', array(), Visualizer_Plugin::VERSION );
606 - $this->_addFilter( 'media_upload_tabs', 'setupVisualizerTab' );
607 - wp_enqueue_media();
685 + if ( $suffix !== $this->_libraryPage ) {
686 + return;
687 + }
688 +
689 + wp_enqueue_style( 'visualizer-library', VISUALIZER_ABSURL . 'css/library.css', array(), Visualizer_Plugin::VERSION );
690 + $this->_addFilter( 'media_upload_tabs', 'setupVisualizerTab' );
691 + wp_enqueue_media();
692 + wp_enqueue_script(
693 + 'visualizer-library',
694 + VISUALIZER_ABSURL . 'js/library.js',
695 + array(
696 + 'jquery',
697 + 'media-views',
698 + 'clipboard',
699 + ),
700 + Visualizer_Plugin::VERSION,
701 + true
702 + );
703 +
704 + wp_enqueue_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
705 +
706 + $has_d3 = false;
707 + $query = $this->getQuery();
708 + while ( $query->have_posts() ) {
709 + $chart = $query->next_post();
710 + $library = $this->load_chart_type( $chart->ID );
711 + if ( is_null( $library ) ) {
712 + continue;
713 + }
608 714 wp_enqueue_script(
609 - 'visualizer-library',
610 - VISUALIZER_ABSURL . 'js/library.js',
611 - array(
612 - 'jquery',
613 - 'media-views',
614 - 'clipboard',
615 - ),
715 + "visualizer-render-$library",
716 + VISUALIZER_ABSURL . 'js/render-facade.js',
717 + apply_filters( 'visualizer_assets_render', array( 'visualizer-library', 'visualizer-customization' ), true ),
616 718 Visualizer_Plugin::VERSION,
617 719 true
618 720 );
721 + if ( 'd3' === $library ) {
722 + $has_d3 = true;
723 + }
724 + }
619 725
620 - wp_enqueue_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
726 + if ( $has_d3 ) {
727 + $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php';
728 + if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
729 + /**
730 + * Ignore missing build asset in source checkout.
731 + *
732 + * @phpstan-ignore-next-line
733 + */
734 + $d3_asset = include $d3_renderer_asset;
735 + wp_register_script(
736 + 'visualizer-d3-renderer',
737 + VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js',
738 + array_merge( $d3_asset['dependencies'], array( 'jquery' ) ),
739 + $d3_asset['version'],
740 + true
741 + );
742 + wp_enqueue_script( 'visualizer-d3-renderer' );
743 + }
744 + wp_localize_script(
745 + 'visualizer-d3-renderer',
746 + 'vizD3Renderer',
747 + array(
748 + 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js',
749 + )
750 + );
751 + }
621 752
622 - $query = $this->getQuery();
623 - while ( $query->have_posts() ) {
624 - $chart = $query->next_post();
625 - $library = $this->load_chart_type( $chart->ID );
626 - if ( is_null( $library ) ) {
627 - continue;
628 - }
629 - wp_enqueue_script(
630 - "visualizer-render-$library",
631 - VISUALIZER_ABSURL . 'js/render-facade.js',
632 - apply_filters( 'visualizer_assets_render', array( 'visualizer-library', 'visualizer-customization' ), true ),
633 - Visualizer_Plugin::VERSION,
753 + $chart_builder_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/ChartBuilder/build/index.asset.php';
754 + if ( file_exists( $chart_builder_asset ) ) {
755 + /**
756 + * Ignore missing build asset in source checkout.
757 + *
758 + * @phpstan-ignore-next-line
759 + */
760 + $asset = include $chart_builder_asset;
761 + $chart_builder_deps = $asset['dependencies'];
762 + if ( ! wp_script_is( 'visualizer-handsontable', 'registered' ) && defined( 'Visualizer_Pro_ABSURL' ) && defined( 'VISUALIZER_PRO_VERSION' ) ) {
763 + wp_register_script(
764 + 'visualizer-handsontable',
765 + Visualizer_Pro_ABSURL . 'vendor/handsontable/dist/handsontable.full.min.js',
766 + array(),
767 + VISUALIZER_PRO_VERSION,
634 768 true
635 769 );
636 770 }
771 + if ( ! wp_style_is( 'visualizer-handsontable', 'registered' ) && defined( 'Visualizer_Pro_ABSURL' ) && defined( 'VISUALIZER_PRO_VERSION' ) ) {
772 + wp_register_style(
773 + 'visualizer-handsontable',
774 + Visualizer_Pro_ABSURL . 'vendor/handsontable/dist/handsontable.full.min.css',
775 + array(),
776 + VISUALIZER_PRO_VERSION
777 + );
778 + }
779 + if ( wp_script_is( 'visualizer-handsontable', 'registered' ) ) {
780 + wp_enqueue_script( 'visualizer-handsontable' );
781 + $chart_builder_deps = array_unique( array_merge( $chart_builder_deps, array( 'visualizer-handsontable' ) ) );
782 + }
783 + if ( wp_style_is( 'visualizer-handsontable', 'registered' ) ) {
784 + wp_enqueue_style( 'visualizer-handsontable' );
785 + }
786 + wp_enqueue_script(
787 + 'visualizer-chart-builder',
788 + VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/index.js',
789 + $chart_builder_deps,
790 + $asset['version'],
791 + true
792 + );
793 + wp_enqueue_style(
794 + 'visualizer-chart-builder',
795 + VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/style-index.css',
796 + array(),
797 + $asset['version']
798 + );
799 + $chart_builder_css = VISUALIZER_ABSPATH . '/classes/Visualizer/ChartBuilder/build/index.css';
800 + if ( file_exists( $chart_builder_css ) ) {
801 + wp_enqueue_style(
802 + 'visualizer-chart-builder-runtime',
803 + VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/index.css',
804 + array( 'visualizer-chart-builder' ),
805 + $asset['version']
806 + );
807 + }
808 + wp_localize_script(
809 + 'visualizer-chart-builder',
810 + 'vizAIBuilder',
811 + array(
812 + 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
813 + 'nonce' => wp_create_nonce( 'visualizer-ai-builder' ),
814 + 'isPro' => Visualizer_Module::is_pro(),
815 + 'upgradeUrl' => tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'aiBuilderUpgrade', 'dataSource' ),
816 + )
817 + );
637 818 }
819 +
820 + do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'library' );
638 821 }
639 822
640 823 /**
824 + * Enqueue script for support page.
825 + *
826 + * @param string $hook_suffix Current hook.
827 + */
828 + public function enqueue_support_page( $hook_suffix ) {
829 + if ( $this->_supportPage !== $hook_suffix ) {
830 + return;
831 + }
832 + do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'support' );
833 + }
834 +
835 + /**
641 836 * Adds visualizer tab for media upload tabs array.
642 837 *
643 838 * @since 1.0.0
644 839 *
@@ -670,9 +865,9 @@
670 865 Visualizer_Plugin::NAME,
671 866 __( 'Chart Library', 'visualizer' ),
672 867 __( 'Chart Library', 'visualizer' ),
673 868 'edit_posts',
674 - admin_url( 'admin.php?page=' . Visualizer_Plugin::NAME )
869 + 'admin.php?page=' . Visualizer_Plugin::NAME
675 870 );
676 871 add_submenu_page(
677 872 Visualizer_Plugin::NAME,
678 873 __( 'Add New Chart', 'visualizer' ),
@@ -677,12 +872,22 @@
677 872 Visualizer_Plugin::NAME,
678 873 __( 'Add New Chart', 'visualizer' ),
679 874 __( 'Add New Chart', 'visualizer' ),
680 875 'edit_posts',
681 - admin_url( 'admin.php?page=' . Visualizer_Plugin::NAME . '&vaction=addnew' )
876 + 'admin.php?page=' . Visualizer_Plugin::NAME . '&vaction=addnew'
682 877 );
683 - add_submenu_page(
878 +
879 + $this->_settingsPage = add_submenu_page(
684 880 Visualizer_Plugin::NAME,
881 + __( 'Settings', 'visualizer' ),
882 + __( 'Settings', 'visualizer' ),
883 + 'manage_options',
884 + 'viz-settings',
885 + array( $this, 'renderSettingsPage' )
886 + );
887 +
888 + $this->_supportPage = add_submenu_page(
889 + Visualizer_Plugin::NAME,
685 890 __( 'Support', 'visualizer' ),
686 891 __( 'Support', 'visualizer' ) . '<span class="dashicons dashicons-editor-help more-features-icon" style="width: 17px; height: 17px; margin-left: 4px; color: #ffca54; font-size: 17px; vertical-align: -3px;"></span>',
687 892 'edit_posts',
688 893 'viz-support',
@@ -688,22 +893,12 @@
688 893 'viz-support',
689 894 array( $this, 'renderSupportPage' )
690 895 );
691 896
692 - if ( ! Visualizer_Module::is_pro() ) {
693 - add_submenu_page(
694 - Visualizer_Plugin::NAME,
695 - __( 'Get Visualizer Pro', 'visualizer' ),
696 - __( 'Get Visualizer Pro', 'visualizer' ),
697 - 'edit_posts',
698 - 'viz-get-pro',
699 - '__return_null'
700 - );
701 - add_action( 'admin_footer', array( $this, 'handleGetProSubMenu' ) );
702 - }
703 897 remove_submenu_page( Visualizer_Plugin::NAME, Visualizer_Plugin::NAME );
704 898
705 899 add_action( "load-{$this->_libraryPage}", array( $this, 'addScreenOptions' ) );
900 + add_action( 'admin_footer', array( $this, 'handleGetProSubMenu' ) );
706 901 }
707 902
708 903 /**
709 904 * Handle get pro plugin submenu.
@@ -708,41 +903,41 @@
708 903 /**
709 904 * Handle get pro plugin submenu.
710 905 */
711 906 public function handleGetProSubMenu() {
712 - ?>
907 + if ( ! Visualizer_Module::is_pro() ) {
908 + ?>
713 909 <style type="text/css">
714 - #toplevel_page_visualizer ul.wp-submenu li.wp-first-item + li + li + li {
910 + #toplevel_page_visualizer ul.wp-submenu li:last-child {
715 911 background: #FF7E65;
716 912 font-size: 14px;
717 913 font-weight: 600;
718 914 color: #fff;
719 915 }
720 - #toplevel_page_visualizer ul.wp-submenu li.wp-first-item + li + li + li > a {
916 + #toplevel_page_visualizer ul.wp-submenu li:last-child > a > span {
721 917 color: #fff !important;
722 918 }
723 - #toplevel_page_visualizer ul.wp-submenu li.wp-first-item + li + li + li > a:hover {
919 + #toplevel_page_visualizer ul.wp-submenu li:last-child > a:hover {
724 920 box-shadow: inherit;
725 921 }
726 - #toplevel_page_visualizer ul.wp-submenu li.wp-first-item + li + li + li + li {
727 - display: none;
922 + </style>
923 + <?php
924 + if ( get_option( 'visualizer_fresh_install', false ) ) {
925 + ?>
926 + <style type="text/css">
927 + #toplevel_page_visualizer ul.wp-submenu li.wp-first-item + li + li + li {
928 + display: none;
929 + }
930 + </style>
931 + <?php
728 932 }
729 - </style>
730 - <script type="text/javascript">
731 - jQuery( document ).ready( function() {
732 - jQuery( '#toplevel_page_visualizer' ).on( 'click', 'li:not(.wp-submenu-head, .wp-first-item):eq(2)', function( e ) {
733 - e.preventDefault();
734 - window.open( '<?php echo tsdk_utmify( 'https://themeisle.com/plugins/visualizer-charts-and-graphs/upgrade/', 'toplevel' ); ?>', '_blank' );
735 - } );
736 - } );
737 - </script>
738 - <?php
933 + }
739 934 }
740 935
741 936 /**
742 937 * Adds the screen options for pagination.
743 938 */
744 - function addScreenOptions() {
939 + public function addScreenOptions() {
745 940 $screen = get_current_screen();
746 941
747 942 // bail if it's some other page.
748 943 if ( ! is_object( $screen ) || $screen->id !== $this->_libraryPage ) {
@@ -759,9 +954,9 @@
759 954
760 955 /**
761 956 * Returns the screen option for pagination.
762 957 */
763 - function setScreenOptions( $status, $option, $value ) {
958 + public function setScreenOptions( $status, $option, $value ) {
764 959 if ( 'visualizer_library_per_page' === $option ) {
765 960 return $value;
766 961 }
767 962 }
@@ -913,8 +1108,91 @@
913 1108 include_once VISUALIZER_ABSPATH . '/templates/support.php';
914 1109 }
915 1110
916 1111 /**
1112 + * Enqueues scripts/styles for the Settings page.
1113 + *
1114 + * @access public
1115 + */
1116 + public function enqueueSettingsScripts( string $hook_suffix ): void {
1117 + if ( $this->_settingsPage !== $hook_suffix ) {
1118 + return;
1119 + }
1120 + wp_enqueue_style( 'wp-color-picker' );
1121 + wp_enqueue_script( 'wp-color-picker' );
1122 + }
1123 +
1124 + /**
1125 + * Renders the global style settings page.
1126 + *
1127 + * @access public
1128 + */
1129 + public function renderSettingsPage(): void {
1130 + if ( ! current_user_can( 'manage_options' ) ) {
1131 + wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'visualizer' ) );
1132 + }
1133 + $settings = self::getGlobalSettings();
1134 + include_once VISUALIZER_ABSPATH . '/templates/global-settings.php';
1135 + }
1136 +
1137 + /**
1138 + * Returns the global style settings option.
1139 + *
1140 + * @return array<string, string>
1141 + * @access public
1142 + * @static
1143 + */
1144 + public static function getGlobalSettings(): array {
1145 + $defaults = array(
1146 + 'color_primary' => '',
1147 + 'color_secondary' => '',
1148 + 'apply_existing' => '0',
1149 + );
1150 + $saved = get_option( self::OPTION_GLOBAL_SETTINGS, array() );
1151 + return wp_parse_args( $saved, $defaults );
1152 + }
1153 +
1154 + /**
1155 + * Handles saving of the global style settings.
1156 + *
1157 + * @access public
1158 + */
1159 + public function saveGlobalSettings(): void {
1160 + if ( ! current_user_can( 'manage_options' ) ) {
1161 + wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'visualizer' ) );
1162 + }
1163 +
1164 + check_admin_referer( 'visualizer_save_global_settings' );
1165 +
1166 + $color_primary = sanitize_hex_color( wp_unslash( $_POST['visualizer_color_primary'] ?? '' ) );
1167 + $color_secondary = sanitize_hex_color( wp_unslash( $_POST['visualizer_color_secondary'] ?? '' ) );
1168 + $apply_existing = ! empty( $_POST['visualizer_apply_existing'] ) ? '1' : '0';
1169 + $clear = ! empty( $_POST['visualizer_clear_settings'] );
1170 +
1171 + if ( $clear ) {
1172 + delete_option( self::OPTION_GLOBAL_SETTINGS );
1173 + } else {
1174 + $settings = array(
1175 + 'color_primary' => $color_primary ? $color_primary : '',
1176 + 'color_secondary' => $color_secondary ? $color_secondary : '',
1177 + 'apply_existing' => $apply_existing,
1178 + );
1179 + update_option( self::OPTION_GLOBAL_SETTINGS, $settings );
1180 + }
1181 +
1182 + wp_safe_redirect(
1183 + add_query_arg(
1184 + array(
1185 + 'page' => 'viz-settings',
1186 + 'updated' => $clear ? 'cleared' : 'true',
1187 + ),
1188 + admin_url( 'admin.php' )
1189 + )
1190 + );
1191 + exit;
1192 + }
1193 +
1194 + /**
917 1195 * Renders visualizer library page.
918 1196 *
919 1197 * @since 1.0.0
920 1198 *
@@ -998,8 +1276,9 @@
998 1276
999 1277 // add chart to the array
1000 1278 $charts[ $id ] = array(
1001 1279 'id' => $chart->ID,
1280 + 'title' => $chart->post_title,
1002 1281 'type' => $type,
1003 1282 'series' => $series,
1004 1283 'settings' => $settings,
1005 1284 'data' => $data,
@@ -1004,11 +1283,15 @@
1004 1283 'settings' => $settings,
1005 1284 'data' => $data,
1006 1285 'library' => $library,
1007 1286 );
1287 + if ( 'd3' === $library ) {
1288 + $charts[ $id ]['code'] = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
1289 + }
1008 1290 }
1009 1291 // enqueue charts array
1010 1292 $ajaxurl = admin_url( 'admin-ajax.php' );
1293 +
1011 1294 wp_localize_script(
1012 1295 'visualizer-library',
1013 1296 'visualizer',
1014 1297 array(
@@ -1045,8 +1328,9 @@
1045 1328 'i10n' => array(
1046 1329 'copied' => __( 'The shortcode has been copied to your clipboard. Hit Ctrl-V/Cmd-V to paste it.', 'visualizer' ),
1047 1330 'conflict' => __( 'We have detected a potential conflict with another component that prevents Visualizer from functioning properly. Please disable any of the following components if they are activated on your instance: Modern Events Calendar plugin, Acronix plugin. In case the aforementioned components are not activated or you continue to see this error message, please disable all other plugins and enable them one by one to find out the component that is causing the conflict.', 'visualizer' ),
1048 1331 ),
1332 + 'is_pro_user' => Visualizer_Module::is_pro(),
1049 1333 )
1050 1334 );
1051 1335 // render library page
1052 1336 $render = new Visualizer_Render_Library();
@@ -1062,8 +1346,9 @@
1062 1346 'total' => $query->max_num_pages,
1063 1347 'type' => 'array',
1064 1348 )
1065 1349 );
1350 +
1066 1351 $render->render();
1067 1352 }
1068 1353
1069 1354 /**
@@ -1105,19 +1390,36 @@
1105 1390 *
1106 1391 * @return array Updated array of plugin meta links.
1107 1392 */
1108 1393 public function getPluginMetaLinks( $plugin_meta, $plugin_file ) {
1394 + if ( Visualizer_Module::is_pro() ) {
1395 + return $plugin_meta;
1396 + }
1397 +
1398 + // Also suppress the upsell when Pro is installed but not currently active.
1399 + if ( ! function_exists( 'get_plugins' ) ) {
1400 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
1401 + }
1402 + foreach ( array_keys( get_plugins() ) as $installed_plugin ) {
1403 + if ( false !== strpos( $installed_plugin, 'visualizer-pro' ) ) {
1404 + return $plugin_meta;
1405 + }
1406 + }
1407 +
1109 1408 if ( $plugin_file === plugin_basename( VISUALIZER_BASEFILE ) ) {
1409 + $is_black_friday = apply_filters( 'themeisle_sdk_is_black_friday_sale', false );
1110 1410 // knowledge base link
1111 1411 $plugin_meta[] = sprintf(
1112 1412 '<a href="' . VISUALIZER_MAIN_DOC . '" target="_blank">%s</a>',
1113 1413 esc_html__( 'Docs', 'visualizer' )
1114 1414 );
1115 - // flattr link
1116 - $plugin_meta[] = sprintf(
1117 - '<a style="color:red" href="' . tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'pluginrow' ) . '" target="_blank">%s</a>',
1118 - esc_html__( 'Pro Addon', 'visualizer' )
1119 - );
1415 + if ( ! $is_black_friday ) {
1416 + // flattr link
1417 + $plugin_meta[] = sprintf(
1418 + '<a style="color:red" href="' . tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'pluginrow' ) . '" target="_blank">%s</a>',
1419 + esc_html__( 'Get Visualizer Pro', 'visualizer' )
1420 + );
1421 + }
1120 1422 }
1121 1423
1122 1424 return $plugin_meta;
1123 1425 }
@@ -1122,17 +1424,39 @@
1122 1424 return $plugin_meta;
1123 1425 }
1124 1426
1125 1427 /**
1126 - * If check is existing user.
1428 + * Returns true when premium chart types should be enabled for the current user.
1127 1429 *
1128 - * @return bool Default false
1430 + * Pro 1.9.0+ hooks the 'visualizer_is_pro' filter and returns true only when
1431 + * the license is valid. Pre-1.9.0 Pro defined a Visualizer_Pro class instead;
1432 + * we detect that as a fallback so those legacy installs still work.
1433 + *
1434 + * Passing false as the filter default ensures the constant VISUALIZER_PRO
1435 + * (which is set to true whenever the Pro class exists, regardless of license
1436 + * state) cannot bypass the license check.
1437 + *
1438 + * @return bool
1129 1439 */
1130 - public static function proFeaturesLocked() {
1131 - if ( Visualizer_Module::is_pro() ) {
1440 + public static function proFeaturesEnabled() {
1441 + $is_pro_filter = apply_filters( 'visualizer_is_pro', false );
1442 +
1443 + // Pro 1.9.0+: filter is hooked and returns true only with a valid license.
1444 + if ( $is_pro_filter ) {
1132 1445 return true;
1133 1446 }
1134 - return 'yes' === get_option( 'visualizer-new-user', 'yes' ) ? false : true;
1447 +
1448 + // Pro is installed (active) but the license check above did not pass.
1449 + // Do NOT fall through to the legacy "old user" path — that path exists
1450 + // only for sites that never had Pro installed. If Pro is present but
1451 + // the license is inactive we should lock, regardless of chart history.
1452 + if ( class_exists( 'Visualizer_Pro', false ) ) {
1453 + return false;
1454 + }
1455 +
1456 + // No Pro installed at all: grant legacy access to existing users so
1457 + // their charts are not suddenly broken when they upgrade the free plugin.
1458 + return 'yes' === get_option( 'visualizer-new-user' ) ? false : true;
1135 1459 }
1136 1460
1137 1461 /**
1138 1462 * Multilingual Support.
@@ -1190,6 +1514,144 @@
1190 1514 if ( isset( $types[ $type ] ) ) {
1191 1515 return ! empty( $types[ $type ]['enabled'] );
1192 1516 }
1193 1517 return false;
1518 + }
1519 +
1520 + /**
1521 + * Get the survey metadata.
1522 + *
1523 + * @param array $data The data for survey in Formbricks format.
1524 + * @param string $page_slug The slug of the loaded page.
1525 + *
1526 + * @return array The survey metadata.
1527 + */
1528 + public function get_survey_metadata( $data, $page_slug ) {
1529 + $install_date = get_option( 'visualizer_install', time() );
1530 + $install_days_number = intval( ( time() - $install_date ) / DAY_IN_SECONDS );
1531 +
1532 + $license_status = apply_filters( 'product_visualizer_license_status', 'invalid' );
1533 + $license_plan = apply_filters( 'product_visualizer_license_plan', false );
1534 + $license_key = apply_filters( 'product_visualizer_license_key', false );
1535 +
1536 + $plugin_data = get_plugin_data( VISUALIZER_BASEFILE, false, false );
1537 + $plugin_version = '';
1538 +
1539 + if ( ! empty( $plugin_data['Version'] ) ) {
1540 + $plugin_version = $plugin_data['Version'];
1541 + }
1542 +
1543 + $count_charts_cache_key = 'visualizer_count_charts';
1544 + $charts_number = get_transient( $count_charts_cache_key );
1545 +
1546 + if ( false === $charts_number ) {
1547 + $charts_number = $this->count_charts( 100 );
1548 + set_transient( $count_charts_cache_key, $charts_number, 100 === $charts_number ? WEEK_IN_SECONDS : 6 * HOUR_IN_SECONDS );
1549 + } else {
1550 + $charts_number = strval( $charts_number );
1551 + }
1552 +
1553 + $data = array(
1554 + 'environmentId' => 'cltef8cut1s7wyyfxy3rlxzs5',
1555 + 'attributes' => array(
1556 + 'free_version' => $plugin_version,
1557 + 'pro_version' => defined( 'VISUALIZER_PRO_VERSION' ) ? VISUALIZER_PRO_VERSION : '',
1558 + 'license_status' => $license_status,
1559 + 'install_days_number' => $install_days_number,
1560 + 'charts_number' => $charts_number,
1561 + ),
1562 + );
1563 +
1564 + if ( ! empty( $license_plan ) ) {
1565 + $data['attributes']['plan'] = $license_plan;
1566 + }
1567 +
1568 + if ( ! empty( $license_key ) ) {
1569 + $data['attributes']['license_key'] = apply_filters( 'themeisle_sdk_secret_masking', $license_key );
1570 + }
1571 +
1572 + return $data;
1573 + }
1574 +
1575 + /**
1576 + * Count the charts.
1577 + *
1578 + * @param int $limit The count limit (optional).
1579 + *
1580 + * @return int The number of charts.
1581 + */
1582 + public function count_charts( $limit = -1 ) {
1583 + $args = array(
1584 + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1585 + 'post_status' => 'publish',
1586 + 'posts_per_page' => $limit,
1587 + 'fields' => 'ids',
1588 + );
1589 +
1590 + $query = new WP_Query( $args);
1591 + return $query->post_count;
1592 + }
1593 +
1594 + /**
1595 + * Set the black friday data.
1596 + *
1597 + * @param array $configs The configuration array for the loaded products.
1598 + * @return array
1599 + */
1600 + public function add_black_friday_data( $configs ) {
1601 + $config = $configs['default'];
1602 +
1603 + $message = __( 'Database queries, private charts, auto-sync. Go beyond basic charts. Exclusively for existing Visualizer users.', 'visualizer' );
1604 + $cta_label = __( 'Get Visualizer Pro', 'visualizer' );
1605 +
1606 + $plan = apply_filters( 'product_visualizer_license_plan', 0 );
1607 + $license = apply_filters( 'product_visualizer_license_key', false );
1608 + $status = apply_filters( 'product_visualizer_license_status', false );
1609 + $pro_product_slug = defined( 'VISUALIZER_PRO_BASEFILE' ) ? basename( dirname( VISUALIZER_PRO_BASEFILE ) ) : '';
1610 +
1611 + $is_pro = 'valid' === $status;
1612 + $is_expired = 'expired' === $status || 'active-expired' === $status;
1613 +
1614 + if ( $is_pro ) {
1615 + // translators: %s is the discount percentage.
1616 + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - up to %s off', 'visualizer' ), '30%' );
1617 + // translators: %1$s - discount, %2$s - discount.
1618 + $message = sprintf( __( 'Upgrade your Visualizer Pro plan: %1$s off this week. Already on the plan you need? Renew early and save up to %2$s.', 'visualizer' ), '30%', '20%' );
1619 + $cta_label = __( 'See your options', 'visualizer' );
1620 + } elseif ( $is_expired ) {
1621 + // translators: %s is the discount percentage.
1622 + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'visualizer' ), '50%' );
1623 + // translators: %s is the discount percentage.
1624 + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'visualizer' ), '50%' );
1625 + $message = __( 'Your Visualizer Pro features are still here, just locked. Renew at a reduced rate this week.', 'visualizer' );
1626 + $cta_label = __( 'Reactivate now', 'visualizer' );
1627 + } else {
1628 + // translators: %s is the discount percentage.
1629 + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'visualizer' ), '60%' );
1630 + $config['title'] = __( 'Visualizer Pro: 60% off this week', 'visualizer' );
1631 + // translators: %s is the discount percentage.
1632 + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'visualizer' ), '60%' );
1633 + }
1634 +
1635 + $url_params = array(
1636 + 'utm_term' => $is_pro ? 'plan-' . $plan : 'free',
1637 + 'lkey' => ! empty( $license ) ? $license : false,
1638 + 'expired' => $is_expired ? '1' : false,
1639 + );
1640 +
1641 + if ( ( $is_pro || $is_expired ) && ! empty( $pro_product_slug ) ) {
1642 + $config['plugin_meta_targets'] = array( $pro_product_slug );
1643 + }
1644 +
1645 + $config['message'] = $message;
1646 + $config['cta_label'] = $cta_label;
1647 +
1648 + $config['sale_url'] = add_query_arg(
1649 + $url_params,
1650 + tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/vizualizer-bf', 'bfcm', 'visualizer' ) )
1651 + );
1652 +
1653 + $configs[ VISUALIZER_DIRNAME ] = $config;
1654 +
1655 + return $configs;
1194 1656 }
1195 1657 }