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
visualizer / classes / Visualizer / Module / Admin.php

Admin.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.8, at classes/Visualizer/Module/Admin.php

1,658 lines 54.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Eugene Manuilov <eugene@manuilov.org> |
20 // +----------------------------------------------------------------------+
21 /**
22 * The module for all admin stuff.
23 *
24 * @category Visualizer
25 * @package Module
26 *
27 * @since 1.0.0
28 */
29 class Visualizer_Module_Admin extends Visualizer_Module {
30
31 const NAME = __CLASS__;
32
33 const OPTION_GLOBAL_SETTINGS = 'visualizer_global_settings';
34
35 /**
36 * Library page suffix.
37 *
38 * @since 1.0.0
39 *
40 * @access private
41 * @var string
42 */
43 private $_libraryPage;
44
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 /**
62 * Constructor.
63 *
64 * @since 1.0.0
65 *
66 * @access public
67 *
68 * @param Visualizer_Plugin $plugin The instance of the plugin.
69 */
70 public function __construct( Visualizer_Plugin $plugin ) {
71 parent::__construct( $plugin );
72 $this->_addAction( 'load-post.php', 'enqueueMediaScripts' );
73 $this->_addAction( 'load-post-new.php', 'enqueueMediaScripts' );
74 $this->_addAction( 'enqueue_block_editor_assets', 'enqueueMediaScripts' );
75 $this->_addAction( 'admin_footer', 'renderTemplates' );
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' );
80 $this->_addAction( 'admin_menu', 'registerAdminMenu' );
81 $this->_addFilter( 'media_view_strings', 'setupMediaViewStrings' );
82 $this->_addFilter( 'plugin_action_links', 'getPluginActionLinks', 10, 2 );
83 $this->_addFilter( 'plugin_row_meta', 'getPluginMetaLinks', 10, 2 );
84 $this->_addFilter( 'visualizer_feedback_review_trigger', 'feedbackReviewTrigger' );
85 $this->_addFilter( 'themeisle_sdk_blackfriday_data', 'add_black_friday_data' );
86
87 // screen pagination
88 $this->_addFilter( 'set-screen-option', 'setScreenOptions', 10, 3 );
89
90 // revision support.
91 $this->_addFilter( 'wp_revisions_to_keep', 'limitRevisions', null, 10, 2 );
92 $this->_addAction( '_wp_put_post_revision', 'addRevision', null, 10, 1 );
93 $this->_addAction( 'wp_restore_post_revision', 'restoreRevision', null, 10, 2 );
94
95 $this->_addAction( 'visualizer_chart_schedules_spl', 'addSplChartSchedules', null, 10, 3 );
96
97 $this->_addAction( 'admin_init', 'init' );
98
99 $this->_addAction( 'visualizer_chart_languages', 'addMultilingualSupport' );
100
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' ) ) {
108 $this->load_cypress_hooks();
109 }
110 }
111 /**
112 * Display review notice.
113 */
114 public function render_review_notice( $footer_text ) {
115 $current_screen = get_current_screen();
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;
135 }
136
137
138 /**
139 * Define the hooks that are needed for cypress.
140 *
141 * @since 3.4.0
142 * @access private
143 */
144 private function load_cypress_hooks() {
145 // all charts should load on the same page without pagination.
146 add_filter(
147 'visualizer_query_args', function ( $args ) {
148 $args['posts_per_page'] = 20;
149 return $args;
150 }, 10, 1
151 );
152 }
153
154 /**
155 * Add disabled `optgroup` schedules to the drop downs.
156 *
157 * @since ?
158 *
159 * @access public
160 *
161 * @param string $feature The feature for which to add schedules.
162 * @param int $chart_id The chart ID.
163 * @param int $plan The plan number.
164 */
165 public function addSplChartSchedules( $feature, $chart_id, $plan ) {
166 if ( apply_filters( 'visualizer_is_business', false ) ) {
167 return;
168 }
169
170 $is_new_personal = apply_filters( 'visualizer_is_new_personal', false );
171
172 $hours = array(
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' ),
178 );
179
180 switch ( $feature ) {
181 case 'json':
182 case 'csv':
183 // no more schedules if pro is already active.
184 if ( Visualizer_Module::is_pro() && ! $is_new_personal ) {
185 return;
186 }
187 break;
188 case 'wp':
189 // fall-through.
190 case 'db':
191 break;
192 default:
193 return;
194 }
195
196 echo '<optgroup disabled label="' . __( 'Upgrade required', 'visualizer' ) . '">';
197 foreach ( $hours as $hour => $desc ) {
198 echo '<option disabled>' . $desc . '</option>';
199 }
200 echo '</optgroup>';
201 }
202
203 /**
204 * No limits on revisions.
205 */
206 public function limitRevisions( $num, $post ) {
207 if ( Visualizer_Plugin::CPT_VISUALIZER === $post->post_type ) {
208 return -1;
209 }
210 return $num;
211 }
212
213 /**
214 * Add a revision.
215 */
216 public function addRevision( $revision_id ) {
217 $parent_id = wp_is_post_revision( $revision_id );
218 if ( Visualizer_Plugin::CPT_VISUALIZER === get_post_type( $parent_id ) ) {
219 // add the meta data to this revision.
220 $meta = get_post_meta( $parent_id, '', true );
221
222 if ( $meta ) {
223 foreach ( $meta as $key => $value ) {
224 if ( 0 === strpos( $key, 'visualizer' ) ) {
225 add_metadata( 'post', $revision_id, $key, self::maybe_decode_content( $value[0] ) );
226 }
227 }
228 }
229 }
230 }
231
232 /**
233 * Restore a revision.
234 */
235 public function restoreRevision( $post_id, $revision_id ) {
236 if ( Visualizer_Plugin::CPT_VISUALIZER === get_post_type( $post_id ) ) {
237 // get the meta information from the revision.
238 $meta = get_metadata( 'post', $revision_id, '', true );
239
240 // delete all meta information from the post before adding.
241 $post_meta = get_post_meta( $post_id, '', true );
242 if ( $post_meta ) {
243 foreach ( $meta as $key => $value ) {
244 if ( 0 === strpos( $key, 'visualizer' ) ) {
245 delete_post_meta( $post_id, $key );
246 }
247 }
248 }
249
250 if ( $meta ) {
251 foreach ( $meta as $key => $value ) {
252 if ( 0 === strpos( $key, 'visualizer' ) ) {
253 add_post_meta( $post_id, $key, self::maybe_decode_content( $value[0] ) );
254 }
255 }
256 }
257 }
258 }
259
260 /**
261 * Admin init.
262 *
263 * @access public
264 */
265 public function init() {
266 if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' === get_user_option( 'rich_editing' ) ) {
267 $this->_addFilter( 'mce_external_languages', 'add_tinymce_lang', 10, 1 );
268 $this->_addFilter( 'mce_external_plugins', 'tinymce_plugin', 10, 1 );
269 $this->_addFilter( 'mce_buttons', 'register_mce_button', 10, 1 );
270 $this->_addFilter( 'tiny_mce_before_init', 'get_strings_for_block', 10, 1 );
271 }
272 }
273
274 /**
275 * Add the strings required for the TinyMCE buttons for the classic block (not the classic editor).
276 *
277 * @since ?
278 * @access friendly
279 */
280 public function get_strings_for_block( $settings ) {
281 $class = new Visualizer_Module_Language();
282 $strings = $class->get_strings();
283 $array = array( 'visualizer_tinymce_plugin' => json_encode( $strings ) );
284 return array_merge( $settings, $array );
285 }
286
287 /**
288 * Load plugin translation for - TinyMCE API
289 *
290 * @access public
291 * @param array $arr The tinymce_lang array.
292 * @return array
293 */
294 public function add_tinymce_lang( $arr ) {
295 $ui_lang = VISUALIZER_ABSPATH . '/classes/Visualizer/Module/Language.php';
296 $ui_lang = apply_filters( 'visualizer_ui_lang_filter', $ui_lang );
297 $arr[] = $ui_lang;
298 return $arr;
299 }
300
301 /**
302 * Load custom js options - TinyMCE API
303 *
304 * @access public
305 * @param array $plugin_array The tinymce plugin array.
306 * @return array
307 */
308 public function tinymce_plugin( $plugin_array ) {
309 $plugin_array['visualizer_mce_button'] = VISUALIZER_ABSURL . 'js/mce.js';
310 return $plugin_array;
311 }
312
313 /**
314 * Register new button in the editor
315 *
316 * @access public
317 * @param array $buttons The tinymce buttons array.
318 * @return array
319 */
320 public function register_mce_button( $buttons ) {
321 array_push( $buttons, 'visualizer_mce_button' );
322 return $buttons;
323 }
324
325 /**
326 * Whether to show the feedback review or not.
327 *
328 * @access public
329 */
330 public function feedbackReviewTrigger( $dumb ) {
331 $query = new WP_Query(
332 array(
333 'posts_per_page' => 50,
334 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
335 'fields' => 'ids',
336 'update_post_meta_cache' => false,
337 'update_post_term_cache' => false,
338 )
339 );
340
341 if ( $query->have_posts() && $query->post_count > 0 ) {
342 return true;
343 }
344 return false;
345 }
346
347 /**
348 * Enqueues media scripts and styles.
349 *
350 * @since 1.0.0
351 * @uses wp_enqueue_style To enqueue style file.
352 * @uses wp_enqueue_script To enqueue script file.
353 *
354 * @access public
355 */
356 public function enqueueMediaScripts() {
357 global $typenow;
358 global $current_screen;
359
360 if ( post_type_supports( $typenow, 'editor' ) || $current_screen->id === 'widgets' || $current_screen->id === 'customize' ) {
361 wp_enqueue_style( 'visualizer-media', VISUALIZER_ABSURL . 'css/media.css', array( 'media-views' ), Visualizer_Plugin::VERSION );
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
386 // Load all the assets for the different libraries we support.
387 $deps = array(
388 Visualizer_Render_Sidebar_Google::enqueue_assets( array( 'media-editor' ) ),
389 Visualizer_Render_Sidebar_Type_DataTable_Tabular::enqueue_assets( array( 'media-editor' ) ),
390 );
391
392 wp_enqueue_script( 'visualizer-media-model', VISUALIZER_ABSURL . 'js/media/model.js', $deps, Visualizer_Plugin::VERSION, true );
393 wp_enqueue_script( 'visualizer-media-collection', VISUALIZER_ABSURL . 'js/media/collection.js', array( 'visualizer-media-model' ), Visualizer_Plugin::VERSION, true );
394 wp_enqueue_script( 'visualizer-media-controller', VISUALIZER_ABSURL . 'js/media/controller.js', array( 'visualizer-media-collection' ), Visualizer_Plugin::VERSION, true );
395 wp_enqueue_script( 'visualizer-media-view', VISUALIZER_ABSURL . 'js/media/view.js', array( 'visualizer-media-controller' ), Visualizer_Plugin::VERSION, true );
396 wp_localize_script(
397 'visualizer-media-view',
398 'visualizer',
399 array(
400 'i10n' => array(
401 'insert' => __( 'Insert Chart', 'visualizer' ),
402 ),
403 )
404 );
405 wp_enqueue_script( 'visualizer-media-toolbar', VISUALIZER_ABSURL . 'js/media/toolbar.js', array( 'visualizer-media-view' ), Visualizer_Plugin::VERSION, true );
406 wp_enqueue_script( 'visualizer-media', VISUALIZER_ABSURL . 'js/media.js', array( 'visualizer-media-toolbar' ), Visualizer_Plugin::VERSION, true );
407 }
408 }
409
410 /**
411 * Extends media view strings with visualizer strings.
412 *
413 * @since 1.0.0
414 *
415 * @access public
416 *
417 * @param array $strings The array of media view strings.
418 *
419 * @return array The extended array of media view strings.
420 */
421 public function setupMediaViewStrings( $strings ) {
422 $chart_types = self::_getChartTypesLocalized( true, true, true );
423 $strings['visualizer'] = array(
424 'actions' => array(
425 'get_charts' => Visualizer_Plugin::ACTION_GET_CHARTS,
426 'delete_chart' => Visualizer_Plugin::ACTION_DELETE_CHART,
427 ),
428 'controller' => array(
429 'title' => esc_html__( 'Visualizations', 'visualizer' ),
430 ),
431 'routers' => array(
432 'library' => esc_html__( 'Insert from Library', 'visualizer' ),
433 'create' => esc_html__( 'Create New', 'visualizer' ),
434 ),
435 'library' => array(
436 'filters' => $chart_types,
437 'types' => array_keys( $chart_types ),
438 ),
439 'nonce' => wp_create_nonce( Visualizer_Plugin::ACTION_GET_CHARTS ),
440 'buildurl' => esc_url( add_query_arg( 'action', Visualizer_Plugin::ACTION_CREATE_CHART, admin_url( 'admin-ajax.php' ) ) ),
441 );
442
443 return $strings;
444 }
445
446 /**
447 * Returns associated array of chart types and localized names.
448 *
449 * @since 1.0.0
450 *
451 * @static
452 * @access private
453 * @return array The associated array of chart types with localized names.
454 */
455 public static function _getChartTypesLocalized( $enabledOnly = false, $get2Darray = false, $add_select = false, $where = null ) {
456 $additional = array();
457 if ( $add_select ) {
458 $additional['select'] = array(
459 'name' => esc_html__( 'All', 'visualizer' ),
460 'enabled' => true,
461 );
462 }
463
464 $enabled = self::proFeaturesEnabled();
465
466 $types = array_merge(
467 $additional,
468 array(
469 'tabular' => array(
470 'name' => esc_html__( 'Table', 'visualizer' ),
471 'enabled' => true,
472 'supports' => $enabled ? array( 'Google Charts', 'DataTable' ) : array( 'DataTable' ),
473 ),
474 'pie' => array(
475 'name' => esc_html__( 'Pie/Donut', 'visualizer' ),
476 'enabled' => true,
477 'supports' => array( 'Google Charts', 'ChartJS' ),
478 ),
479 'line' => array(
480 'name' => esc_html__( 'Line', 'visualizer' ),
481 'enabled' => true,
482 'supports' => array( 'Google Charts', 'ChartJS' ),
483 ),
484 'bar' => array(
485 'name' => esc_html__( 'Bar', 'visualizer' ),
486 'enabled' => true,
487 'supports' => $enabled ? array( 'Google Charts', 'ChartJS' ) : array( 'Google Charts' ),
488 ),
489 'area' => array(
490 'name' => esc_html__( 'Area', 'visualizer' ),
491 'enabled' => $enabled,
492 // in ChartJS, the fill option is used to make Line chart an area: https://www.chartjs.org/docs/latest/charts/area.html
493 'supports' => array( 'Google Charts' ),
494 'demo_url' => 'https://demo.themeisle.com/visualizer/area-chart/',
495 ),
496 'geo' => array(
497 'name' => esc_html__( 'Geo', 'visualizer' ),
498 'enabled' => $enabled,
499 'supports' => array( 'Google Charts' ),
500 'demo_url' => 'https://demo.themeisle.com/visualizer/geo-chart/',
501 ),
502 'column' => array(
503 'name' => esc_html__( 'Column', 'visualizer' ),
504 'enabled' => $enabled,
505 'supports' => array( 'Google Charts', 'ChartJS' ),
506 'demo_url' => 'https://demo.themeisle.com/visualizer/column-chart/',
507 ),
508 'bubble' => array(
509 'name' => esc_html__( 'Bubble', 'visualizer' ),
510 'enabled' => $enabled,
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.
512 'supports' => array( 'Google Charts' ),
513 'demo_url' => 'https://demo.themeisle.com/visualizer/bubble-chart/',
514 ),
515 'scatter' => array(
516 'name' => esc_html__( 'Scatter', 'visualizer' ),
517 'enabled' => $enabled,
518 'supports' => array( 'Google Charts' ),
519 'demo_url' => 'https://demo.themeisle.com/visualizer/scatter-chart/',
520 ),
521 'gauge' => array(
522 'name' => esc_html__( 'Gauge', 'visualizer' ),
523 'enabled' => $enabled,
524 'supports' => array( 'Google Charts' ),
525 'demo_url' => 'https://demo.themeisle.com/visualizer/gauge-chart/',
526 ),
527 'candlestick' => array(
528 'name' => esc_html__( 'Candlestick', 'visualizer' ),
529 'enabled' => $enabled,
530 'supports' => array( 'Google Charts' ),
531 'demo_url' => 'https://demo.themeisle.com/visualizer/candlestick-chart/',
532 ),
533 // pro types
534 'timeline' => array(
535 'name' => esc_html__( 'Timeline', 'visualizer' ),
536 'enabled' => false,
537 'supports' => array( 'Google Charts', 'ChartJS' ),
538 'demo_url' => 'https://demo.themeisle.com/visualizer/timeline-chart/',
539 ),
540 'combo' => array(
541 'name' => esc_html__( 'Combo', 'visualizer' ),
542 'enabled' => false,
543 'supports' => array( 'Google Charts' ),
544 'demo_url' => 'https://demo.themeisle.com/visualizer/combo-chart/',
545 ),
546 'polarArea' => array(
547 'name' => esc_html__( 'Polar Area', 'visualizer' ),
548 'enabled' => false,
549 'supports' => array( 'ChartJS' ),
550 'demo_url' => 'https://demo.themeisle.com/visualizer/polar-area-chart/',
551 ),
552 'radar' => array(
553 'name' => esc_html__( 'Radar/Spider', 'visualizer' ),
554 'enabled' => false,
555 'supports' => array( 'ChartJS' ),
556 'demo_url' => 'https://demo.themeisle.com/visualizer/radar-spider-chart/',
557 ),
558 )
559 );
560 $types = apply_filters( 'visualizer_pro_chart_types', $types );
561 if ( $enabledOnly ) {
562 $filtered = array();
563 foreach ( $types as $type => $array ) {
564 if ( ! is_array( $array ) ) {
565 // support for old pro
566 $array = array( 'enabled' => true, 'name' => $array );
567 }
568 // backward compatibility for PRO before v1.9.0
569 if ( ! array_key_exists( 'supports', $array ) ) {
570 $array['supports'] = array( 'Google Charts' );
571 }
572 if ( ! $array['enabled'] ) {
573 continue;
574 }
575 $filtered[ $type ] = $array;
576 }
577 $types = $filtered;
578 }
579 if ( $get2Darray ) {
580 $doubleD = array();
581 foreach ( $types as $type => $array ) {
582 if ( ! is_array( $array ) ) {
583 // support for old pro
584 $array = array( 'enabled' => true, 'name' => $array );
585 }
586 // backward compatibility for PRO before v1.9.0
587 if ( ! array_key_exists( 'supports', $array ) ) {
588 $array['supports'] = array( 'Google Charts' );
589 }
590 $doubleD[ $type ] = $array['name'];
591 }
592 $types = $doubleD;
593 }
594
595 return self::handleDeprecatedCharts( $types, $enabledOnly, $get2Darray, $where );
596 }
597
598 /**
599 * Handle (soon-to-be) deprecated charts.
600 */
601 private static function handleDeprecatedCharts( $types, $enabledOnly, $get2Darray, $where ) {
602 $deprecated = array();
603
604 switch ( $where ) {
605 case 'types':
606 // fall-through
607 case 'library':
608 // if a user has a Gauge/Candlestick chart, then let them keep using it.
609 if ( ! Visualizer_Module::is_pro() ) {
610 if ( ! self::hasChartType( 'gauge' ) ) {
611 if ( $get2Darray ) {
612 $deprecated[] = 'gauge';
613 } else {
614 $types['gauge']['enabled'] = false;
615 }
616 }
617 if ( ! self::hasChartType( 'candlestick' ) ) {
618 if ( $get2Darray ) {
619 $deprecated[] = 'candlestick';
620 } else {
621 $types['candlestick']['enabled'] = false;
622 }
623 }
624 }
625 break;
626 default:
627 // if a user has a Gauge/Candlestick chart, then let them keep using it.
628 if ( ! Visualizer_Module::is_pro() ) {
629 if ( ! self::hasChartType( 'gauge' ) ) {
630 if ( $get2Darray ) {
631 $deprecated[] = 'gauge';
632 } else {
633 $types['gauge']['enabled'] = false;
634 }
635 }
636 if ( ! self::hasChartType( 'candlestick' ) ) {
637 if ( $get2Darray ) {
638 $deprecated[] = 'candlestick';
639 } else {
640 $types['candlestick']['enabled'] = false;
641 }
642 }
643 }
644 }
645
646 if ( $deprecated ) {
647 foreach ( $deprecated as $type ) {
648 unset( $types[ $type ] );
649 }
650 }
651
652 return $types;
653 }
654
655 /**
656 * Renders templates to use in media popup.
657 *
658 * @since 1.0.0
659 * @global string $pagenow The name of the current page.
660 *
661 * @access public
662 */
663 public function renderTemplates() {
664 global $pagenow;
665 if ( 'post.php' !== $pagenow && 'post-new.php' !== $pagenow ) {
666 return;
667 }
668 $render = new Visualizer_Render_Templates();
669 $render->render();
670 }
671
672 /**
673 * Enqueues library scripts and styles.
674 *
675 * @since 1.0.0
676 * @uses wp_enqueue_style() To enqueue library stylesheet.
677 * @uses wp_enqueue_script() To enqueue javascript file.
678 * @uses wp_enqueue_media() To enqueue media stuff.
679 *
680 * @access public
681 *
682 * @param string $suffix The current page suffix.
683 */
684 public function enqueueLibraryScripts( $suffix ) {
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 }
714 wp_enqueue_script(
715 "visualizer-render-$library",
716 VISUALIZER_ABSURL . 'js/render-facade.js',
717 apply_filters( 'visualizer_assets_render', array( 'visualizer-library', 'visualizer-customization' ), true ),
718 Visualizer_Plugin::VERSION,
719 true
720 );
721 if ( 'd3' === $library ) {
722 $has_d3 = true;
723 }
724 }
725
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 }
752
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,
768 true
769 );
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 );
818 }
819
820 do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'library' );
821 }
822
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 /**
836 * Adds visualizer tab for media upload tabs array.
837 *
838 * @since 1.0.0
839 *
840 * @access public
841 *
842 * @param array $tabs The array of media upload tabs.
843 *
844 * @return array Extended array of media upload tabs.
845 */
846 public function setupVisualizerTab( $tabs ) {
847 $tabs['visualizer'] = 'Visualizer';
848
849 return $tabs;
850 }
851
852 /**
853 * Registers admin menu for visualizer library.
854 *
855 * @since 1.0.0
856 *
857 * @access public
858 */
859 public function registerAdminMenu() {
860 $svg_base64_icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNzdweCIgaGVpZ2h0PSI3N3B4IiB2aWV3Qm94PSIwIDAgNzcgNzciIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjYgKDY3NDkxKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Db21iaW5lZCBTaGFwZTwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJQcm9kdWN0LVBhZ2UiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJXb3JkUHJlc3MtcGx1Z2lucyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE5MC4wMDAwMDAsIC00MzUuMDAwMDAwKSIgZmlsbD0iIzM5QzNEMiI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMjguNSw1MTIgQzIwNy4yMzcwMzcsNTEyIDE5MCw0OTQuNzYyOTYzIDE5MCw0NzMuNSBDMTkwLDQ1Mi4yMzcwMzcgMjA3LjIzNzAzNyw0MzUgMjI4LjUsNDM1IEMyNDkuNzYyOTYzLDQzNSAyNjcsNDUyLjIzNzAzNyAyNjcsNDczLjUgQzI2Nyw0OTQuNzYyOTYzIDI0OS43NjI5NjMsNTEyIDIyOC41LDUxMiBaIE0yNDYuODQzNzUsNDgzLjU5MjA1OSBMMjE1LjYyNSw0ODMuNTkyMDU5IEwyMTUuNjI1LDQ2MC44MjY1NDQgQzIxNS42MjUsNDYwLjE2NDU0NyAyMTUuMTA3NTc4LDQ1OS42MjgzNTkgMjE0LjQ2ODc1LDQ1OS42MjgzNTkgTDIxMi4xNTYyNSw0NTkuNjI4MzU5IEMyMTEuNTE3NDIyLDQ1OS42MjgzNTkgMjExLDQ2MC4xNjQ1NDcgMjExLDQ2MC44MjY1NDQgTDIxMSw0ODUuOTg4NDI5IEMyMTEsNDg3LjMxMTY3NCAyMTIuMDM1NTY2LDQ4OC4zODQ3OTggMjEzLjMxMjUsNDg4LjM4NDc5OCBMMjQ2Ljg0Mzc1LDQ4OC4zODQ3OTggQzI0Ny40ODI1NzgsNDg4LjM4NDc5OCAyNDgsNDg3Ljg0ODYxMSAyNDgsNDg3LjE4NjYxMyBMMjQ4LDQ4NC43OTAyNDQgQzI0OCw0ODQuMTI4MjQ2IDI0Ny40ODI1NzgsNDgzLjU5MjA1OSAyNDYuODQzNzUsNDgzLjU5MjA1OSBaIE0yNDQuNTMxMjUsNDYyLjAyNDcyOSBMMjM1Ljk5OTU3LDQ2Mi4wMjQ3MjkgQzIzNC40NTQ1MzEsNDYyLjAyNDcyOSAyMzMuNjgwNTY2LDQ2My45NjA1NDcgMjM0Ljc3MzIyMyw0NjUuMDkyODMyIEwyMzcuMTE0NjI5LDQ2Ny41MTkxNTYgTDIzMS44MTI1LDQ3My4wMTQzMzIgTDIyNi41MTAzNzEsNDY3LjUxOTkwNSBDMjI1LjYwNzA1MSw0NjYuNTgzODIzIDIyNC4xNDI5NDksNDY2LjU4MzgyMyAyMjMuMjQwMzUyLDQ2Ny41MTk5MDUgTDIxOC4yNzY0MjYsNDcyLjY2Mzg2MyBDMjE3LjgyNDc2Niw0NzMuMTMxOTA0IDIxNy44MjQ3NjYsNDczLjg5MDUwNSAyMTguMjc2NDI2LDQ3NC4zNTg1NDYgTDIxOS45MTEwNzQsNDc2LjA1MjQ4IEMyMjAuMzYyNzM0LDQ3Ni41MjA1MjEgMjIxLjA5NDc4NSw0NzYuNTIwNTIxIDIyMS41NDY0NDUsNDc2LjA1MjQ4IEwyMjQuODc1LDQ3Mi42MDI0NTYgTDIzMC4xNzcxMjksNDc4LjA5Njg4MyBDMjMxLjA4MDQ0OSw0NzkuMDMyOTY1IDIzMi41NDQ1NTEsNDc5LjAzMjk2NSAyMzMuNDQ3MTQ4LDQ3OC4wOTY4ODMgTDI0MC4zODQ2NDgsNDcwLjkwNzc3MyBMMjQyLjcyNjA1NSw0NzMuMzM0MDk4IEMyNDMuODE4NzExLDQ3NC40NjYzODIgMjQ1LjY4Njc3Nyw0NzMuNjY0MzQ3IDI0NS42ODY3NzcsNDcyLjA2MzI3MyBMMjQ1LjY4Njc3Nyw0NjMuMjIyOTE0IEMyNDUuNjg3NSw0NjIuNTYwOTE3IDI0NS4xNzAwNzgsNDYyLjAyNDcyOSAyNDQuNTMxMjUsNDYyLjAyNDcyOSBaIiBpZD0iQ29tYmluZWQtU2hhcGUiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==';
861
862 $this->_libraryPage = add_menu_page( __( 'Visualizer', 'visualizer' ), __( 'Visualizer', 'visualizer' ), 'edit_posts', Visualizer_Plugin::NAME, array( $this, 'renderLibraryPage' ), $svg_base64_icon, 99.7666 );
863
864 add_submenu_page(
865 Visualizer_Plugin::NAME,
866 __( 'Chart Library', 'visualizer' ),
867 __( 'Chart Library', 'visualizer' ),
868 'edit_posts',
869 'admin.php?page=' . Visualizer_Plugin::NAME
870 );
871 add_submenu_page(
872 Visualizer_Plugin::NAME,
873 __( 'Add New Chart', 'visualizer' ),
874 __( 'Add New Chart', 'visualizer' ),
875 'edit_posts',
876 'admin.php?page=' . Visualizer_Plugin::NAME . '&vaction=addnew'
877 );
878
879 $this->_settingsPage = add_submenu_page(
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,
890 __( 'Support', 'visualizer' ),
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>',
892 'edit_posts',
893 'viz-support',
894 array( $this, 'renderSupportPage' )
895 );
896
897 remove_submenu_page( Visualizer_Plugin::NAME, Visualizer_Plugin::NAME );
898
899 add_action( "load-{$this->_libraryPage}", array( $this, 'addScreenOptions' ) );
900 add_action( 'admin_footer', array( $this, 'handleGetProSubMenu' ) );
901 }
902
903 /**
904 * Handle get pro plugin submenu.
905 */
906 public function handleGetProSubMenu() {
907 if ( ! Visualizer_Module::is_pro() ) {
908 ?>
909 <style type="text/css">
910 #toplevel_page_visualizer ul.wp-submenu li:last-child {
911 background: #FF7E65;
912 font-size: 14px;
913 font-weight: 600;
914 color: #fff;
915 }
916 #toplevel_page_visualizer ul.wp-submenu li:last-child > a > span {
917 color: #fff !important;
918 }
919 #toplevel_page_visualizer ul.wp-submenu li:last-child > a:hover {
920 box-shadow: inherit;
921 }
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
932 }
933 }
934 }
935
936 /**
937 * Adds the screen options for pagination.
938 */
939 public function addScreenOptions() {
940 $screen = get_current_screen();
941
942 // bail if it's some other page.
943 if ( ! is_object( $screen ) || $screen->id !== $this->_libraryPage ) {
944 return;
945 }
946
947 $args = array(
948 'label' => __( 'Number of charts per page:', 'visualizer' ),
949 'default' => 6,
950 'option' => 'visualizer_library_per_page',
951 );
952 add_screen_option( 'per_page', $args );
953 }
954
955 /**
956 * Returns the screen option for pagination.
957 */
958 public function setScreenOptions( $status, $option, $value ) {
959 if ( 'visualizer_library_per_page' === $option ) {
960 return $value;
961 }
962 }
963
964 /**
965 * Adds the display filters to be used in the meta_query while displaying the charts.
966 */
967 private function getDisplayFilters( &$query_args ) {
968 $query = array();
969 global $sitepress;
970 if ( Visualizer_Module::is_pro() && ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) ) {
971 $current_lang = icl_get_current_language();
972 if ( in_array( $current_lang, array( 'all', icl_get_default_language() ), true ) ) {
973 $query[] = array(
974 'key' => 'chart_lang',
975 'compare' => 'NOT EXISTS',
976 );
977 } else {
978 $query[] = array(
979 'key' => 'chart_lang',
980 'value' => $current_lang,
981 'compare' => '=',
982 );
983 }
984 }
985
986 // add chart type filter to the query arguments
987 $type = filter_input( INPUT_GET, 'type' );
988 if ( $type && in_array( $type, Visualizer_Plugin::getChartTypes(), true ) ) {
989 $query[] = array(
990 'key' => Visualizer_Plugin::CF_CHART_TYPE,
991 'value' => $type,
992 'compare' => '=',
993 );
994 }
995
996 // add chart library filter to the query arguments
997 $library = filter_input( INPUT_GET, 'library' );
998 if ( $library ) {
999 $query[] = array(
1000 'key' => Visualizer_Plugin::CF_CHART_LIBRARY,
1001 'value' => $library,
1002 'compare' => '=',
1003 );
1004 }
1005
1006 // add date filter to the query arguments
1007 $date = filter_input( INPUT_GET, 'date' );
1008 $possible = array_keys( Visualizer_Plugin::getSupportedDateFilter() );
1009 if ( $date && in_array( $date, $possible, true ) ) {
1010 $query_args['date_query'] = array(
1011 'after' => "$date -1 day",
1012 'inclusive' => true,
1013 );
1014 }
1015
1016 // add source filter to the query arguments
1017 $source = filter_input( INPUT_GET, 'source' );
1018 if ( $source ) {
1019 $source = ucwords( $source );
1020 $source = "Visualizer_Source_{$source}";
1021 $query[] = array(
1022 'key' => Visualizer_Plugin::CF_SOURCE,
1023 'value' => $source,
1024 'compare' => '=',
1025 );
1026 }
1027
1028 $query_args['meta_query'] = $query;
1029
1030 $orderby = filter_input( INPUT_GET, 'orderby' );
1031 $order = filter_input( INPUT_GET, 'order' );
1032 if ( $orderby ) {
1033 $query_args['order_by'] = $orderby;
1034 }
1035 $query_args['order'] = empty( $order ) ? 'desc' : $order;
1036 }
1037
1038 /**
1039 * Get the instance of WP_Query that fetches the charts as per the given criteria.
1040 */
1041 private function getQuery() {
1042 static $q;
1043 if ( ! is_null( $q ) ) {
1044 return $q;
1045 }
1046
1047 // get current page
1048 $page = filter_input(
1049 INPUT_GET,
1050 'vpage',
1051 FILTER_VALIDATE_INT,
1052 array(
1053 'options' => array(
1054 'min_range' => 1,
1055 'default' => 1,
1056 ),
1057 )
1058 );
1059
1060 $per_page = 6;
1061 $screen = get_current_screen();
1062 if ( $screen ) {
1063 // retrieve the per_page option
1064 $screen_option = $screen->get_option( 'per_page', 'option' );
1065 // retrieve the value stored for the current user
1066 $user = get_current_user_id();
1067 $per_page = get_user_meta( $user, $screen_option, true );
1068 if ( empty( $per_page ) || $per_page < 1 ) {
1069 // nothing set, get the default value
1070 $per_page = $screen->get_option( 'per_page', 'default' );
1071 }
1072 }
1073
1074 // the initial query arguments to fetch charts
1075 $query_args = array(
1076 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1077 'posts_per_page' => $per_page,
1078 'paged' => $page,
1079 );
1080
1081 $this->getDisplayFilters( $query_args );
1082
1083 // Added by Ash/Upwork
1084 $filterByMeta = ! empty( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1085 if ( $filterByMeta ) {
1086 $query = array(
1087 'key' => Visualizer_Plugin::CF_SETTINGS,
1088 'value' => $filterByMeta,
1089 'compare' => 'LIKE',
1090 );
1091 $meta = isset( $query_args['meta_query'] ) ? $query_args['meta_query'] : array();
1092 $meta[] = $query;
1093 $query_args['meta_query'] = $meta;
1094 }
1095 $q = new WP_Query( apply_filters( 'visualizer_query_args', $query_args ) );
1096 return $q;
1097 }
1098
1099 /**
1100 * Renders support page.
1101 *
1102 * @since 3.3.0
1103 *
1104 * @access public
1105 */
1106 public function renderSupportPage() {
1107 wp_enqueue_style( 'visualizer-upsell', VISUALIZER_ABSURL . 'css/upsell.css', array(), Visualizer_Plugin::VERSION );
1108 include_once VISUALIZER_ABSPATH . '/templates/support.php';
1109 }
1110
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 /**
1195 * Renders visualizer library page.
1196 *
1197 * @since 1.0.0
1198 *
1199 * @access public
1200 */
1201 public function renderLibraryPage() {
1202 $charts = array();
1203 $query = $this->getQuery();
1204
1205 // get current page
1206 $page = filter_input(
1207 INPUT_GET,
1208 'vpage',
1209 FILTER_VALIDATE_INT,
1210 array(
1211 'options' => array(
1212 'min_range' => 1,
1213 'default' => 1,
1214 ),
1215 )
1216 );
1217 // add chart type filter to the query arguments
1218 $filter = filter_input( INPUT_GET, 'type' );
1219 if ( ! ( $filter && in_array( $filter, Visualizer_Plugin::getChartTypes(), true ) ) ) {
1220 $filter = 'all';
1221 }
1222
1223 $css = '';
1224 while ( $query->have_posts() ) {
1225 $chart = $query->next_post();
1226
1227 // if the user has updated a chart and instead of saving it, has closed the modal. If the user refreshes, they should get the original chart.
1228 $chart = $this->handleExistingRevisions( $chart->ID, $chart );
1229 // refresh a "live" db query chart.
1230 $chart = apply_filters( 'visualizer_schedule_refresh_chart', $chart, $chart->ID, false );
1231
1232 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
1233
1234 // fetch and update settings
1235 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1236
1237 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
1238 if ( ! empty( $atts['settings'] ) ) {
1239 $settings = apply_filters( $atts['settings'], $settings, $chart->ID, $type );
1240 }
1241
1242 if ( $settings ) {
1243 unset( $settings['height'], $settings['width'], $settings['chartArea'] );
1244 }
1245
1246 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ), $chart->ID, $type );
1247 $data = self::get_chart_data( $chart, $type );
1248
1249 // Set default setting series when add manual chart data - Only during first creation.
1250 if ( isset( $settings['series'] ) && ! empty( $series ) ) {
1251 $count_series = count( $series ) - count( $settings['series'] );
1252 if ( $count_series > 1 ) {
1253 $clone_last_series = end( $settings['series'] );
1254 $clone_last_series = array_fill_keys( array_keys( $clone_last_series ), '' );
1255 foreach ( range( 1, $count_series ) as $item ) {
1256 $settings['series'][] = $clone_last_series;
1257 }
1258 }
1259 }
1260
1261 $library = $this->load_chart_type( $chart->ID );
1262
1263 $id = 'visualizer-' . $chart->ID;
1264 $arguments = $this->get_inline_custom_css( $id, $settings );
1265 if ( ! empty( $arguments ) ) {
1266 $css .= $arguments[0];
1267 $settings = $arguments[1];
1268 }
1269
1270 if ( isset( $settings['controls']['ui']['labelStacking'] ) ) {
1271 unset( $settings['controls']['ui']['labelStacking'] );
1272 }
1273 if ( isset( $settings['controls']['ui']['orientation'] ) ) {
1274 unset( $settings['controls']['ui']['orientation'] );
1275 }
1276
1277 // add chart to the array
1278 $charts[ $id ] = array(
1279 'id' => $chart->ID,
1280 'title' => $chart->post_title,
1281 'type' => $type,
1282 'series' => $series,
1283 'settings' => $settings,
1284 'data' => $data,
1285 'library' => $library,
1286 );
1287 if ( 'd3' === $library ) {
1288 $charts[ $id ]['code'] = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
1289 }
1290 }
1291 // enqueue charts array
1292 $ajaxurl = admin_url( 'admin-ajax.php' );
1293
1294 wp_localize_script(
1295 'visualizer-library',
1296 'visualizer',
1297 array(
1298 'language' => $this->get_language(),
1299 'map_api_key' => get_option( 'visualizer-map-api-key' ),
1300 'charts' => $charts,
1301 'urls' => array(
1302 'base' => esc_url( add_query_arg( array( 'vpage' => false, 'vaction' => false ) ) ),
1303 'create' => esc_url(
1304 add_query_arg(
1305 array(
1306 'action' => Visualizer_Plugin::ACTION_CREATE_CHART,
1307 'library' => 'yes',
1308 'type' => isset( $_GET['type'] ) ? $_GET['type'] : '',
1309 'chart-library' => isset( $_GET['chart-library'] ) ? $_GET['chart-library'] : '',
1310 'vaction' => false,
1311 ),
1312 $ajaxurl
1313 )
1314 ),
1315 'edit' => esc_url(
1316 add_query_arg(
1317 array(
1318 'action' => Visualizer_Plugin::ACTION_EDIT_CHART,
1319 'library' => 'yes',
1320 'vaction' => false,
1321 ),
1322 $ajaxurl
1323 )
1324 ),
1325 ),
1326 'page_type' => 'library',
1327 'is_front' => false,
1328 'i10n' => array(
1329 'copied' => __( 'The shortcode has been copied to your clipboard. Hit Ctrl-V/Cmd-V to paste it.', 'visualizer' ),
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' ),
1331 ),
1332 'is_pro_user' => Visualizer_Module::is_pro(),
1333 )
1334 );
1335 // render library page
1336 $render = new Visualizer_Render_Library();
1337 $render->charts = $charts;
1338 $render->type = $filter;
1339 $render->types = self::_getChartTypesLocalized( false, false, false, true );
1340 $render->custom_css = $css;
1341 $render->pagination = paginate_links(
1342 array(
1343 'base' => add_query_arg( array( 'vpage' => '%#%', 'vaction' => false ) ),
1344 'format' => '',
1345 'current' => $page,
1346 'total' => $query->max_num_pages,
1347 'type' => 'array',
1348 )
1349 );
1350
1351 $render->render();
1352 }
1353
1354 /**
1355 * Updates the plugin's action links, which will be rendered at the plugins table.
1356 *
1357 * @since 1.0.0
1358 *
1359 * @access public
1360 *
1361 * @param array $links The array of original action links.
1362 * @param string $file The plugin basename.
1363 *
1364 * @return array Updated array of action links.
1365 */
1366 public function getPluginActionLinks( $links, $file ) {
1367 if ( $file === plugin_basename( VISUALIZER_BASEFILE ) ) {
1368 array_unshift(
1369 $links,
1370 sprintf(
1371 '<a href="%s">%s</a>',
1372 admin_url( 'admin.php?page=' . Visualizer_Plugin::NAME ),
1373 esc_html__( 'Library', 'visualizer' )
1374 )
1375 );
1376 }
1377
1378 return $links;
1379 }
1380
1381 /**
1382 * Updates the plugin's meta links, which will be rendered at the plugins table.
1383 *
1384 * @since 1.0.0
1385 *
1386 * @access public
1387 *
1388 * @param array $plugin_meta The array of a plugin meta links.
1389 * @param string $plugin_file The plugin's basename.
1390 *
1391 * @return array Updated array of plugin meta links.
1392 */
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
1408 if ( $plugin_file === plugin_basename( VISUALIZER_BASEFILE ) ) {
1409 $is_black_friday = apply_filters( 'themeisle_sdk_is_black_friday_sale', false );
1410 // knowledge base link
1411 $plugin_meta[] = sprintf(
1412 '<a href="' . VISUALIZER_MAIN_DOC . '" target="_blank">%s</a>',
1413 esc_html__( 'Docs', 'visualizer' )
1414 );
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 }
1422 }
1423
1424 return $plugin_meta;
1425 }
1426
1427 /**
1428 * Returns true when premium chart types should be enabled for the current user.
1429 *
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
1439 */
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 ) {
1445 return true;
1446 }
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;
1459 }
1460
1461 /**
1462 * Multilingual Support.
1463 *
1464 * @param int $chart_id Chart ID.
1465 * @return bool Default false
1466 */
1467 public function addMultilingualSupport( $chart_id ) {
1468 global $sitepress;
1469 if ( Visualizer_Module::is_pro() ) {
1470 return;
1471 }
1472 if ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) {
1473 $language = icl_get_languages();
1474 $current_lang = icl_get_current_language();
1475 $default_lang = icl_get_default_language();
1476 $post_info = wpml_get_language_information( null, $chart_id );
1477 $translations = array();
1478 if ( ! empty( $post_info ) && ( $default_lang === $post_info['language_code'] ) ) {
1479 $trid = $sitepress->get_element_trid( $chart_id, 'post_' . Visualizer_Plugin::CPT_VISUALIZER );
1480 $translations = $sitepress->get_element_translations( $trid );
1481 }
1482 if ( empty( $translations ) ) {
1483 return;
1484 }
1485 ?>
1486 <hr><div class="visualizer-languages-list only-pro">
1487 <?php
1488 foreach ( $language as $lang ) {
1489 $lang_code = $lang['code'];
1490 if ( $current_lang !== $lang_code ) {
1491 ?>
1492 <a href="javascript:;">
1493 <img src="<?php echo esc_url( $lang['country_flag_url'] ); ?>" alt="<?php echo esc_attr( $lang['translated_name'] ); ?>">
1494 </a>
1495 <?php
1496 }
1497 }
1498 ?>
1499 <a href="<?php echo tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'wpml-support', 'visualizer_render_char' ); ?>"target="_blank"><?php esc_html_e( 'Upgrade to PRO to active this translation for charts', 'visualizer' ); ?></a>
1500 </div>
1501 <?php
1502 }
1503 }
1504
1505 /**
1506 * Check chart is enabled or not.
1507 *
1508 * @param string $type Chart ID.
1509 *
1510 * @return bool Default false
1511 */
1512 public static function checkChartStatus( $type ) {
1513 $types = self::_getChartTypesLocalized( false, false, false, true );
1514 if ( isset( $types[ $type ] ) ) {
1515 return ! empty( $types[ $type ]['enabled'] );
1516 }
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;
1656 }
1657 }
1658