PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.0
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.0
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.0, at classes/Visualizer/Module/Admin.php

1,634 lines 52.9 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_logger_data', 'getLoggerData' );
85 $this->_addFilter( 'visualizer_feedback_review_trigger', 'feedbackReviewTrigger' );
86 $this->_addFilter( 'themeisle_sdk_blackfriday_data', 'add_black_friday_data' );
87
88 // screen pagination
89 $this->_addFilter( 'set-screen-option', 'setScreenOptions', 10, 3 );
90
91 // revision support.
92 $this->_addFilter( 'wp_revisions_to_keep', 'limitRevisions', null, 10, 2 );
93 $this->_addAction( '_wp_put_post_revision', 'addRevision', null, 10, 1 );
94 $this->_addAction( 'wp_restore_post_revision', 'restoreRevision', null, 10, 2 );
95
96 $this->_addAction( 'visualizer_chart_schedules_spl', 'addSplChartSchedules', null, 10, 3 );
97
98 $this->_addAction( 'admin_init', 'init' );
99
100 $this->_addAction( 'visualizer_chart_languages', 'addMultilingualSupport' );
101
102 $this->_addFilter( 'admin_footer_text', 'render_review_notice' );
103
104 if ( ! defined( 'TI_E2E_TESTING' ) ) {
105 $this->_addFilter( 'themeisle-sdk/survey/' . VISUALIZER_DIRNAME, 'get_survey_metadata', 10, 2 );
106 }
107
108 if ( defined( 'TI_E2E_TESTING' ) ) {
109 $this->load_cypress_hooks();
110 }
111 }
112 /**
113 * Display review notice.
114 */
115 public function render_review_notice( $footer_text ) {
116 $current_screen = get_current_screen();
117
118 $visualizer_page_ids = array( 'toplevel_page_visualizer', 'visualizer_page_viz-support', 'visualizer_page_ti-about-visualizer' );
119
120 if ( ! empty( $current_screen ) && isset( $current_screen->id ) ) {
121 foreach ( $visualizer_page_ids as $page_to_check ) {
122 if ( strpos( $current_screen->id, $page_to_check ) !== false ) {
123 $footer_text = sprintf(
124 // translators: %1$s - the name of the plugin (Visualizer), %2$s - message (You can help us by leaving a), %3$s - HTML entity code.
125 __( 'Enjoying %1$s? %2$s %3$s rating. Thank you for being so supportive!', 'visualizer' ),
126 '<b>Visualizer</b>',
127 esc_html__( 'You can help us by leaving a', 'visualizer' ),
128 '<a href="https://wordpress.org/support/plugin/visualizer/reviews/#new-post" target="_blank">&#9733;&#9733;&#9733;&#9733;&#9733;</a>'
129 );
130 break;
131 }
132 }
133 }
134
135 return $footer_text;
136 }
137
138
139 /**
140 * Define the hooks that are needed for cypress.
141 *
142 * @since 3.4.0
143 * @access private
144 */
145 private function load_cypress_hooks() {
146 // all charts should load on the same page without pagination.
147 add_filter(
148 'visualizer_query_args', function ( $args ) {
149 $args['posts_per_page'] = 20;
150 return $args;
151 }, 10, 1
152 );
153 }
154
155 /**
156 * Add disabled `optgroup` schedules to the drop downs.
157 *
158 * @since ?
159 *
160 * @access public
161 *
162 * @param string $feature The feature for which to add schedules.
163 * @param int $chart_id The chart ID.
164 * @param int $plan The plan number.
165 */
166 public function addSplChartSchedules( $feature, $chart_id, $plan ) {
167 if ( apply_filters( 'visualizer_is_business', false ) ) {
168 return;
169 }
170
171 $is_new_personal = apply_filters( 'visualizer_is_new_personal', false );
172
173 $hours = array(
174 '0.16' => __( '10 minutes', 'visualizer' ),
175 '1' => __( 'Each hour', 'visualizer' ),
176 '12' => __( 'Each 12 hours', 'visualizer' ),
177 '24' => __( 'Each day', 'visualizer' ),
178 '72' => __( 'Each 3 days', 'visualizer' ),
179 );
180
181 switch ( $feature ) {
182 case 'json':
183 case 'csv':
184 // no more schedules if pro is already active.
185 if ( Visualizer_Module::is_pro() && ! $is_new_personal ) {
186 return;
187 }
188 break;
189 case 'wp':
190 // fall-through.
191 case 'db':
192 break;
193 default:
194 return;
195 }
196
197 echo '<optgroup disabled label="' . __( 'Upgrade required', 'visualizer' ) . '">';
198 foreach ( $hours as $hour => $desc ) {
199 echo '<option disabled>' . $desc . '</option>';
200 }
201 echo '</optgroup>';
202 }
203
204 /**
205 * No limits on revisions.
206 */
207 public function limitRevisions( $num, $post ) {
208 if ( Visualizer_Plugin::CPT_VISUALIZER === $post->post_type ) {
209 return -1;
210 }
211 return $num;
212 }
213
214 /**
215 * Add a revision.
216 */
217 public function addRevision( $revision_id ) {
218 $parent_id = wp_is_post_revision( $revision_id );
219 if ( Visualizer_Plugin::CPT_VISUALIZER === get_post_type( $parent_id ) ) {
220 // add the meta data to this revision.
221 $meta = get_post_meta( $parent_id, '', true );
222
223 if ( $meta ) {
224 foreach ( $meta as $key => $value ) {
225 if ( 0 === strpos( $key, 'visualizer' ) ) {
226 add_metadata( 'post', $revision_id, $key, maybe_unserialize( $value[0] ) );
227 }
228 }
229 }
230 }
231 }
232
233 /**
234 * Restore a revision.
235 */
236 public function restoreRevision( $post_id, $revision_id ) {
237 if ( Visualizer_Plugin::CPT_VISUALIZER === get_post_type( $post_id ) ) {
238 // get the meta information from the revision.
239 $meta = get_metadata( 'post', $revision_id, '', true );
240
241 // delete all meta information from the post before adding.
242 $post_meta = get_post_meta( $post_id, '', true );
243 if ( $post_meta ) {
244 foreach ( $meta as $key => $value ) {
245 if ( 0 === strpos( $key, 'visualizer' ) ) {
246 delete_post_meta( $post_id, $key );
247 }
248 }
249 }
250
251 if ( $meta ) {
252 foreach ( $meta as $key => $value ) {
253 if ( 0 === strpos( $key, 'visualizer' ) ) {
254 add_post_meta( $post_id, $key, maybe_unserialize( $value[0] ) );
255 }
256 }
257 }
258 }
259 }
260
261 /**
262 * Admin init.
263 *
264 * @access public
265 */
266 public function init() {
267 if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' === get_user_option( 'rich_editing' ) ) {
268 $this->_addFilter( 'mce_external_languages', 'add_tinymce_lang', 10, 1 );
269 $this->_addFilter( 'mce_external_plugins', 'tinymce_plugin', 10, 1 );
270 $this->_addFilter( 'mce_buttons', 'register_mce_button', 10, 1 );
271 $this->_addFilter( 'tiny_mce_before_init', 'get_strings_for_block', 10, 1 );
272 }
273 }
274
275 /**
276 * Add the strings required for the TinyMCE buttons for the classic block (not the classic editor).
277 *
278 * @since ?
279 * @access friendly
280 */
281 public function get_strings_for_block( $settings ) {
282 $class = new Visualizer_Module_Language();
283 $strings = $class->get_strings();
284 $array = array( 'visualizer_tinymce_plugin' => json_encode( $strings ) );
285 return array_merge( $settings, $array );
286 }
287
288 /**
289 * Load plugin translation for - TinyMCE API
290 *
291 * @access public
292 * @param array $arr The tinymce_lang array.
293 * @return array
294 */
295 public function add_tinymce_lang( $arr ) {
296 $ui_lang = VISUALIZER_ABSPATH . '/classes/Visualizer/Module/Language.php';
297 $ui_lang = apply_filters( 'visualizer_ui_lang_filter', $ui_lang );
298 $arr[] = $ui_lang;
299 return $arr;
300 }
301
302 /**
303 * Load custom js options - TinyMCE API
304 *
305 * @access public
306 * @param array $plugin_array The tinymce plugin array.
307 * @return array
308 */
309 public function tinymce_plugin( $plugin_array ) {
310 $plugin_array['visualizer_mce_button'] = VISUALIZER_ABSURL . 'js/mce.js';
311 return $plugin_array;
312 }
313
314 /**
315 * Register new button in the editor
316 *
317 * @access public
318 * @param array $buttons The tinymce buttons array.
319 * @return array
320 */
321 public function register_mce_button( $buttons ) {
322 array_push( $buttons, 'visualizer_mce_button' );
323 return $buttons;
324 }
325
326 /**
327 * Whether to show the feedback review or not.
328 *
329 * @access public
330 */
331 public function feedbackReviewTrigger( $dumb ) {
332 $query = new WP_Query(
333 array(
334 'posts_per_page' => 50,
335 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
336 'fields' => 'ids',
337 'update_post_meta_cache' => false,
338 'update_post_term_cache' => false,
339 )
340 );
341
342 if ( $query->have_posts() && $query->post_count > 0 ) {
343 return true;
344 }
345 return false;
346 }
347
348 /**
349 * Enqueues media scripts and styles.
350 *
351 * @since 1.0.0
352 * @uses wp_enqueue_style To enqueue style file.
353 * @uses wp_enqueue_script To enqueue script file.
354 *
355 * @access public
356 */
357 public function enqueueMediaScripts() {
358 global $typenow;
359 global $current_screen;
360
361 if ( post_type_supports( $typenow, 'editor' ) || $current_screen->id === 'widgets' || $current_screen->id === 'customize' ) {
362 wp_enqueue_style( 'visualizer-media', VISUALIZER_ABSURL . 'css/media.css', array( 'media-views' ), Visualizer_Plugin::VERSION );
363
364 $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php';
365 if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
366 // @phpstan-ignore-next-line
367 $d3_asset = include $d3_renderer_asset;
368 wp_register_script(
369 'visualizer-d3-renderer',
370 VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js',
371 array_merge( $d3_asset['dependencies'], array( 'jquery' ) ),
372 $d3_asset['version'],
373 true
374 );
375 }
376 if ( wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
377 wp_enqueue_script( 'visualizer-d3-renderer' );
378 wp_localize_script(
379 'visualizer-d3-renderer',
380 'vizD3Renderer',
381 array(
382 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js',
383 )
384 );
385 }
386
387 // Load all the assets for the different libraries we support.
388 $deps = array(
389 Visualizer_Render_Sidebar_Google::enqueue_assets( array( 'media-editor' ) ),
390 Visualizer_Render_Sidebar_Type_DataTable_Tabular::enqueue_assets( array( 'media-editor' ) ),
391 );
392
393 wp_enqueue_script( 'visualizer-media-model', VISUALIZER_ABSURL . 'js/media/model.js', $deps, Visualizer_Plugin::VERSION, true );
394 wp_enqueue_script( 'visualizer-media-collection', VISUALIZER_ABSURL . 'js/media/collection.js', array( 'visualizer-media-model' ), Visualizer_Plugin::VERSION, true );
395 wp_enqueue_script( 'visualizer-media-controller', VISUALIZER_ABSURL . 'js/media/controller.js', array( 'visualizer-media-collection' ), Visualizer_Plugin::VERSION, true );
396 wp_enqueue_script( 'visualizer-media-view', VISUALIZER_ABSURL . 'js/media/view.js', array( 'visualizer-media-controller' ), Visualizer_Plugin::VERSION, true );
397 wp_localize_script(
398 'visualizer-media-view',
399 'visualizer',
400 array(
401 'i10n' => array(
402 'insert' => __( 'Insert Chart', 'visualizer' ),
403 ),
404 )
405 );
406 wp_enqueue_script( 'visualizer-media-toolbar', VISUALIZER_ABSURL . 'js/media/toolbar.js', array( 'visualizer-media-view' ), Visualizer_Plugin::VERSION, true );
407 wp_enqueue_script( 'visualizer-media', VISUALIZER_ABSURL . 'js/media.js', array( 'visualizer-media-toolbar' ), Visualizer_Plugin::VERSION, true );
408 }
409 }
410
411 /**
412 * Extends media view strings with visualizer strings.
413 *
414 * @since 1.0.0
415 *
416 * @access public
417 *
418 * @param array $strings The array of media view strings.
419 *
420 * @return array The extended array of media view strings.
421 */
422 public function setupMediaViewStrings( $strings ) {
423 $chart_types = self::_getChartTypesLocalized( true, true, true );
424 $strings['visualizer'] = array(
425 'actions' => array(
426 'get_charts' => Visualizer_Plugin::ACTION_GET_CHARTS,
427 'delete_chart' => Visualizer_Plugin::ACTION_DELETE_CHART,
428 ),
429 'controller' => array(
430 'title' => esc_html__( 'Visualizations', 'visualizer' ),
431 ),
432 'routers' => array(
433 'library' => esc_html__( 'Insert from Library', 'visualizer' ),
434 'create' => esc_html__( 'Create New', 'visualizer' ),
435 ),
436 'library' => array(
437 'filters' => $chart_types,
438 'types' => array_keys( $chart_types ),
439 ),
440 'nonce' => wp_create_nonce(),
441 'buildurl' => esc_url( add_query_arg( 'action', Visualizer_Plugin::ACTION_CREATE_CHART, admin_url( 'admin-ajax.php' ) ) ),
442 );
443
444 return $strings;
445 }
446
447 /**
448 * Returns associated array of chart types and localized names.
449 *
450 * @since 1.0.0
451 *
452 * @static
453 * @access private
454 * @return array The associated array of chart types with localized names.
455 */
456 public static function _getChartTypesLocalized( $enabledOnly = false, $get2Darray = false, $add_select = false, $where = null ) {
457 $additional = array();
458 if ( $add_select ) {
459 $additional['select'] = array(
460 'name' => esc_html__( 'All', 'visualizer' ),
461 'enabled' => true,
462 );
463 }
464
465 $enabled = self::proFeaturesEnabled();
466
467 $types = array_merge(
468 $additional,
469 array(
470 'tabular' => array(
471 'name' => esc_html__( 'Table', 'visualizer' ),
472 'enabled' => true,
473 'supports' => $enabled ? array( 'Google Charts', 'DataTable' ) : array( 'DataTable' ),
474 ),
475 'pie' => array(
476 'name' => esc_html__( 'Pie/Donut', 'visualizer' ),
477 'enabled' => true,
478 'supports' => array( 'Google Charts', 'ChartJS' ),
479 ),
480 'line' => array(
481 'name' => esc_html__( 'Line', 'visualizer' ),
482 'enabled' => true,
483 'supports' => array( 'Google Charts', 'ChartJS' ),
484 ),
485 'bar' => array(
486 'name' => esc_html__( 'Bar', 'visualizer' ),
487 'enabled' => true,
488 'supports' => $enabled ? array( 'Google Charts', 'ChartJS' ) : array( 'Google Charts' ),
489 ),
490 'area' => array(
491 'name' => esc_html__( 'Area', 'visualizer' ),
492 'enabled' => $enabled,
493 // in ChartJS, the fill option is used to make Line chart an area: https://www.chartjs.org/docs/latest/charts/area.html
494 'supports' => array( 'Google Charts' ),
495 'demo_url' => 'https://demo.themeisle.com/visualizer/area-chart/',
496 ),
497 'geo' => array(
498 'name' => esc_html__( 'Geo', 'visualizer' ),
499 'enabled' => $enabled,
500 'supports' => array( 'Google Charts' ),
501 'demo_url' => 'https://demo.themeisle.com/visualizer/geo-chart/',
502 ),
503 'column' => array(
504 'name' => esc_html__( 'Column', 'visualizer' ),
505 'enabled' => $enabled,
506 'supports' => array( 'Google Charts', 'ChartJS' ),
507 'demo_url' => 'https://demo.themeisle.com/visualizer/column-chart/',
508 ),
509 'bubble' => array(
510 'name' => esc_html__( 'Bubble', 'visualizer' ),
511 'enabled' => $enabled,
512 // chartjs' bubble is ugly looking (and it won't work off the default bubble.csv) so it is being excluded for the time being.
513 'supports' => array( 'Google Charts' ),
514 'demo_url' => 'https://demo.themeisle.com/visualizer/bubble-chart/',
515 ),
516 'scatter' => array(
517 'name' => esc_html__( 'Scatter', 'visualizer' ),
518 'enabled' => $enabled,
519 'supports' => array( 'Google Charts' ),
520 'demo_url' => 'https://demo.themeisle.com/visualizer/scatter-chart/',
521 ),
522 'gauge' => array(
523 'name' => esc_html__( 'Gauge', 'visualizer' ),
524 'enabled' => $enabled,
525 'supports' => array( 'Google Charts' ),
526 'demo_url' => 'https://demo.themeisle.com/visualizer/gauge-chart/',
527 ),
528 'candlestick' => array(
529 'name' => esc_html__( 'Candlestick', 'visualizer' ),
530 'enabled' => $enabled,
531 'supports' => array( 'Google Charts' ),
532 'demo_url' => 'https://demo.themeisle.com/visualizer/candlestick-chart/',
533 ),
534 // pro types
535 'timeline' => array(
536 'name' => esc_html__( 'Timeline', 'visualizer' ),
537 'enabled' => false,
538 'supports' => array( 'Google Charts', 'ChartJS' ),
539 'demo_url' => 'https://demo.themeisle.com/visualizer/timeline-chart/',
540 ),
541 'combo' => array(
542 'name' => esc_html__( 'Combo', 'visualizer' ),
543 'enabled' => false,
544 'supports' => array( 'Google Charts' ),
545 'demo_url' => 'https://demo.themeisle.com/visualizer/combo-chart/',
546 ),
547 'polarArea' => array(
548 'name' => esc_html__( 'Polar Area', 'visualizer' ),
549 'enabled' => false,
550 'supports' => array( 'ChartJS' ),
551 'demo_url' => 'https://demo.themeisle.com/visualizer/polar-area-chart/',
552 ),
553 'radar' => array(
554 'name' => esc_html__( 'Radar/Spider', 'visualizer' ),
555 'enabled' => false,
556 'supports' => array( 'ChartJS' ),
557 'demo_url' => 'https://demo.themeisle.com/visualizer/radar-spider-chart/',
558 ),
559 )
560 );
561 $types = apply_filters( 'visualizer_pro_chart_types', $types );
562 if ( $enabledOnly ) {
563 $filtered = array();
564 foreach ( $types as $type => $array ) {
565 if ( ! is_array( $array ) ) {
566 // support for old pro
567 $array = array( 'enabled' => true, 'name' => $array );
568 }
569 // backward compatibility for PRO before v1.9.0
570 if ( ! array_key_exists( 'supports', $array ) ) {
571 $array['supports'] = array( 'Google Charts' );
572 }
573 if ( ! $array['enabled'] ) {
574 continue;
575 }
576 $filtered[ $type ] = $array;
577 }
578 $types = $filtered;
579 }
580 if ( $get2Darray ) {
581 $doubleD = array();
582 foreach ( $types as $type => $array ) {
583 if ( ! is_array( $array ) ) {
584 // support for old pro
585 $array = array( 'enabled' => true, 'name' => $array );
586 }
587 // backward compatibility for PRO before v1.9.0
588 if ( ! array_key_exists( 'supports', $array ) ) {
589 $array['supports'] = array( 'Google Charts' );
590 }
591 $doubleD[ $type ] = $array['name'];
592 }
593 $types = $doubleD;
594 }
595
596 return self::handleDeprecatedCharts( $types, $enabledOnly, $get2Darray, $where );
597 }
598
599 /**
600 * Handle (soon-to-be) deprecated charts.
601 */
602 private static function handleDeprecatedCharts( $types, $enabledOnly, $get2Darray, $where ) {
603 $deprecated = array();
604
605 switch ( $where ) {
606 case 'types':
607 // fall-through
608 case 'library':
609 // if a user has a Gauge/Candlestick chart, then let them keep using it.
610 if ( ! Visualizer_Module::is_pro() ) {
611 if ( ! self::hasChartType( 'gauge' ) ) {
612 if ( $get2Darray ) {
613 $deprecated[] = 'gauge';
614 } else {
615 $types['gauge']['enabled'] = false;
616 }
617 }
618 if ( ! self::hasChartType( 'candlestick' ) ) {
619 if ( $get2Darray ) {
620 $deprecated[] = 'candlestick';
621 } else {
622 $types['candlestick']['enabled'] = false;
623 }
624 }
625 }
626 break;
627 default:
628 // if a user has a Gauge/Candlestick chart, then let them keep using it.
629 if ( ! Visualizer_Module::is_pro() ) {
630 if ( ! self::hasChartType( 'gauge' ) ) {
631 if ( $get2Darray ) {
632 $deprecated[] = 'gauge';
633 } else {
634 $types['gauge']['enabled'] = false;
635 }
636 }
637 if ( ! self::hasChartType( 'candlestick' ) ) {
638 if ( $get2Darray ) {
639 $deprecated[] = 'candlestick';
640 } else {
641 $types['candlestick']['enabled'] = false;
642 }
643 }
644 }
645 }
646
647 if ( $deprecated ) {
648 foreach ( $deprecated as $type ) {
649 unset( $types[ $type ] );
650 }
651 }
652
653 return $types;
654 }
655
656 /**
657 * Renders templates to use in media popup.
658 *
659 * @since 1.0.0
660 * @global string $pagenow The name of the current page.
661 *
662 * @access public
663 */
664 public function renderTemplates() {
665 global $pagenow;
666 if ( 'post.php' !== $pagenow && 'post-new.php' !== $pagenow ) {
667 return;
668 }
669 $render = new Visualizer_Render_Templates();
670 $render->render();
671 }
672
673 /**
674 * Enqueues library scripts and styles.
675 *
676 * @since 1.0.0
677 * @uses wp_enqueue_style() To enqueue library stylesheet.
678 * @uses wp_enqueue_script() To enqueue javascript file.
679 * @uses wp_enqueue_media() To enqueue media stuff.
680 *
681 * @access public
682 *
683 * @param string $suffix The current page suffix.
684 */
685 public function enqueueLibraryScripts( $suffix ) {
686 if ( $suffix !== $this->_libraryPage ) {
687 return;
688 }
689
690 wp_enqueue_style( 'visualizer-library', VISUALIZER_ABSURL . 'css/library.css', array(), Visualizer_Plugin::VERSION );
691 $this->_addFilter( 'media_upload_tabs', 'setupVisualizerTab' );
692 wp_enqueue_media();
693 wp_enqueue_script(
694 'visualizer-library',
695 VISUALIZER_ABSURL . 'js/library.js',
696 array(
697 'jquery',
698 'media-views',
699 'clipboard',
700 ),
701 Visualizer_Plugin::VERSION,
702 true
703 );
704
705 wp_enqueue_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
706
707 $has_d3 = false;
708 $query = $this->getQuery();
709 while ( $query->have_posts() ) {
710 $chart = $query->next_post();
711 $library = $this->load_chart_type( $chart->ID );
712 if ( is_null( $library ) ) {
713 continue;
714 }
715 wp_enqueue_script(
716 "visualizer-render-$library",
717 VISUALIZER_ABSURL . 'js/render-facade.js',
718 apply_filters( 'visualizer_assets_render', array( 'visualizer-library', 'visualizer-customization' ), true ),
719 Visualizer_Plugin::VERSION,
720 true
721 );
722 if ( 'd3' === $library ) {
723 $has_d3 = true;
724 }
725 }
726
727 if ( $has_d3 ) {
728 $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php';
729 if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
730 /**
731 * Ignore missing build asset in source checkout.
732 *
733 * @phpstan-ignore-next-line
734 */
735 $d3_asset = include $d3_renderer_asset;
736 wp_register_script(
737 'visualizer-d3-renderer',
738 VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js',
739 array_merge( $d3_asset['dependencies'], array( 'jquery' ) ),
740 $d3_asset['version'],
741 true
742 );
743 wp_enqueue_script( 'visualizer-d3-renderer' );
744 }
745 wp_localize_script(
746 'visualizer-d3-renderer',
747 'vizD3Renderer',
748 array(
749 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js',
750 )
751 );
752 }
753
754 $chart_builder_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/ChartBuilder/build/index.asset.php';
755 if ( file_exists( $chart_builder_asset ) ) {
756 /**
757 * Ignore missing build asset in source checkout.
758 *
759 * @phpstan-ignore-next-line
760 */
761 $asset = include $chart_builder_asset;
762 $chart_builder_deps = $asset['dependencies'];
763 if ( ! wp_script_is( 'visualizer-handsontable', 'registered' ) && defined( 'Visualizer_Pro_ABSURL' ) && defined( 'VISUALIZER_PRO_VERSION' ) ) {
764 wp_register_script(
765 'visualizer-handsontable',
766 Visualizer_Pro_ABSURL . 'vendor/handsontable/dist/handsontable.full.min.js',
767 array(),
768 VISUALIZER_PRO_VERSION,
769 true
770 );
771 }
772 if ( ! wp_style_is( 'visualizer-handsontable', 'registered' ) && defined( 'Visualizer_Pro_ABSURL' ) && defined( 'VISUALIZER_PRO_VERSION' ) ) {
773 wp_register_style(
774 'visualizer-handsontable',
775 Visualizer_Pro_ABSURL . 'vendor/handsontable/dist/handsontable.full.min.css',
776 array(),
777 VISUALIZER_PRO_VERSION
778 );
779 }
780 if ( wp_script_is( 'visualizer-handsontable', 'registered' ) ) {
781 wp_enqueue_script( 'visualizer-handsontable' );
782 $chart_builder_deps = array_unique( array_merge( $chart_builder_deps, array( 'visualizer-handsontable' ) ) );
783 }
784 if ( wp_style_is( 'visualizer-handsontable', 'registered' ) ) {
785 wp_enqueue_style( 'visualizer-handsontable' );
786 }
787 wp_enqueue_script(
788 'visualizer-chart-builder',
789 VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/index.js',
790 $chart_builder_deps,
791 $asset['version'],
792 true
793 );
794 wp_enqueue_style(
795 'visualizer-chart-builder',
796 VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/style-index.css',
797 array(),
798 $asset['version']
799 );
800 $chart_builder_css = VISUALIZER_ABSPATH . '/classes/Visualizer/ChartBuilder/build/index.css';
801 if ( file_exists( $chart_builder_css ) ) {
802 wp_enqueue_style(
803 'visualizer-chart-builder-runtime',
804 VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/index.css',
805 array( 'visualizer-chart-builder' ),
806 $asset['version']
807 );
808 }
809 wp_localize_script(
810 'visualizer-chart-builder',
811 'vizAIBuilder',
812 array(
813 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
814 'nonce' => wp_create_nonce( 'visualizer-ai-builder' ),
815 'isPro' => Visualizer_Module::is_pro(),
816 'upgradeUrl' => tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'aiBuilderUpgrade', 'dataSource' ),
817 )
818 );
819 }
820
821 do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'library' );
822 }
823
824 /**
825 * Enqueue script for support page.
826 *
827 * @param string $hook_suffix Current hook.
828 */
829 public function enqueue_support_page( $hook_suffix ) {
830 if ( $this->_supportPage !== $hook_suffix ) {
831 return;
832 }
833 do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'support' );
834 }
835
836 /**
837 * Adds visualizer tab for media upload tabs array.
838 *
839 * @since 1.0.0
840 *
841 * @access public
842 *
843 * @param array $tabs The array of media upload tabs.
844 *
845 * @return array Extended array of media upload tabs.
846 */
847 public function setupVisualizerTab( $tabs ) {
848 $tabs['visualizer'] = 'Visualizer';
849
850 return $tabs;
851 }
852
853 /**
854 * Registers admin menu for visualizer library.
855 *
856 * @since 1.0.0
857 *
858 * @access public
859 */
860 public function registerAdminMenu() {
861 $svg_base64_icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNzdweCIgaGVpZ2h0PSI3N3B4IiB2aWV3Qm94PSIwIDAgNzcgNzciIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjYgKDY3NDkxKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Db21iaW5lZCBTaGFwZTwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJQcm9kdWN0LVBhZ2UiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJXb3JkUHJlc3MtcGx1Z2lucyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE5MC4wMDAwMDAsIC00MzUuMDAwMDAwKSIgZmlsbD0iIzM5QzNEMiI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMjguNSw1MTIgQzIwNy4yMzcwMzcsNTEyIDE5MCw0OTQuNzYyOTYzIDE5MCw0NzMuNSBDMTkwLDQ1Mi4yMzcwMzcgMjA3LjIzNzAzNyw0MzUgMjI4LjUsNDM1IEMyNDkuNzYyOTYzLDQzNSAyNjcsNDUyLjIzNzAzNyAyNjcsNDczLjUgQzI2Nyw0OTQuNzYyOTYzIDI0OS43NjI5NjMsNTEyIDIyOC41LDUxMiBaIE0yNDYuODQzNzUsNDgzLjU5MjA1OSBMMjE1LjYyNSw0ODMuNTkyMDU5IEwyMTUuNjI1LDQ2MC44MjY1NDQgQzIxNS42MjUsNDYwLjE2NDU0NyAyMTUuMTA3NTc4LDQ1OS42MjgzNTkgMjE0LjQ2ODc1LDQ1OS42MjgzNTkgTDIxMi4xNTYyNSw0NTkuNjI4MzU5IEMyMTEuNTE3NDIyLDQ1OS42MjgzNTkgMjExLDQ2MC4xNjQ1NDcgMjExLDQ2MC44MjY1NDQgTDIxMSw0ODUuOTg4NDI5IEMyMTEsNDg3LjMxMTY3NCAyMTIuMDM1NTY2LDQ4OC4zODQ3OTggMjEzLjMxMjUsNDg4LjM4NDc5OCBMMjQ2Ljg0Mzc1LDQ4OC4zODQ3OTggQzI0Ny40ODI1NzgsNDg4LjM4NDc5OCAyNDgsNDg3Ljg0ODYxMSAyNDgsNDg3LjE4NjYxMyBMMjQ4LDQ4NC43OTAyNDQgQzI0OCw0ODQuMTI4MjQ2IDI0Ny40ODI1NzgsNDgzLjU5MjA1OSAyNDYuODQzNzUsNDgzLjU5MjA1OSBaIE0yNDQuNTMxMjUsNDYyLjAyNDcyOSBMMjM1Ljk5OTU3LDQ2Mi4wMjQ3MjkgQzIzNC40NTQ1MzEsNDYyLjAyNDcyOSAyMzMuNjgwNTY2LDQ2My45NjA1NDcgMjM0Ljc3MzIyMyw0NjUuMDkyODMyIEwyMzcuMTE0NjI5LDQ2Ny41MTkxNTYgTDIzMS44MTI1LDQ3My4wMTQzMzIgTDIyNi41MTAzNzEsNDY3LjUxOTkwNSBDMjI1LjYwNzA1MSw0NjYuNTgzODIzIDIyNC4xNDI5NDksNDY2LjU4MzgyMyAyMjMuMjQwMzUyLDQ2Ny41MTk5MDUgTDIxOC4yNzY0MjYsNDcyLjY2Mzg2MyBDMjE3LjgyNDc2Niw0NzMuMTMxOTA0IDIxNy44MjQ3NjYsNDczLjg5MDUwNSAyMTguMjc2NDI2LDQ3NC4zNTg1NDYgTDIxOS45MTEwNzQsNDc2LjA1MjQ4IEMyMjAuMzYyNzM0LDQ3Ni41MjA1MjEgMjIxLjA5NDc4NSw0NzYuNTIwNTIxIDIyMS41NDY0NDUsNDc2LjA1MjQ4IEwyMjQuODc1LDQ3Mi42MDI0NTYgTDIzMC4xNzcxMjksNDc4LjA5Njg4MyBDMjMxLjA4MDQ0OSw0NzkuMDMyOTY1IDIzMi41NDQ1NTEsNDc5LjAzMjk2NSAyMzMuNDQ3MTQ4LDQ3OC4wOTY4ODMgTDI0MC4zODQ2NDgsNDcwLjkwNzc3MyBMMjQyLjcyNjA1NSw0NzMuMzM0MDk4IEMyNDMuODE4NzExLDQ3NC40NjYzODIgMjQ1LjY4Njc3Nyw0NzMuNjY0MzQ3IDI0NS42ODY3NzcsNDcyLjA2MzI3MyBMMjQ1LjY4Njc3Nyw0NjMuMjIyOTE0IEMyNDUuNjg3NSw0NjIuNTYwOTE3IDI0NS4xNzAwNzgsNDYyLjAyNDcyOSAyNDQuNTMxMjUsNDYyLjAyNDcyOSBaIiBpZD0iQ29tYmluZWQtU2hhcGUiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==';
862
863 $this->_libraryPage = add_menu_page( __( 'Visualizer', 'visualizer' ), __( 'Visualizer', 'visualizer' ), 'edit_posts', Visualizer_Plugin::NAME, array( $this, 'renderLibraryPage' ), $svg_base64_icon, 99.7666 );
864
865 add_submenu_page(
866 Visualizer_Plugin::NAME,
867 __( 'Chart Library', 'visualizer' ),
868 __( 'Chart Library', 'visualizer' ),
869 'edit_posts',
870 'admin.php?page=' . Visualizer_Plugin::NAME
871 );
872 add_submenu_page(
873 Visualizer_Plugin::NAME,
874 __( 'Add New Chart', 'visualizer' ),
875 __( 'Add New Chart', 'visualizer' ),
876 'edit_posts',
877 'admin.php?page=' . Visualizer_Plugin::NAME . '&vaction=addnew'
878 );
879
880 $this->_settingsPage = add_submenu_page(
881 Visualizer_Plugin::NAME,
882 __( 'Settings', 'visualizer' ),
883 __( 'Settings', 'visualizer' ),
884 'manage_options',
885 'viz-settings',
886 array( $this, 'renderSettingsPage' )
887 );
888
889 $this->_supportPage = add_submenu_page(
890 Visualizer_Plugin::NAME,
891 __( 'Support', 'visualizer' ),
892 __( '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>',
893 'edit_posts',
894 'viz-support',
895 array( $this, 'renderSupportPage' )
896 );
897
898 remove_submenu_page( Visualizer_Plugin::NAME, Visualizer_Plugin::NAME );
899
900 add_action( "load-{$this->_libraryPage}", array( $this, 'addScreenOptions' ) );
901 add_action( 'admin_footer', array( $this, 'handleGetProSubMenu' ) );
902 }
903
904 /**
905 * Handle get pro plugin submenu.
906 */
907 public function handleGetProSubMenu() {
908 if ( ! Visualizer_Module::is_pro() ) {
909 ?>
910 <style type="text/css">
911 #toplevel_page_visualizer ul.wp-submenu li:last-child {
912 background: #FF7E65;
913 font-size: 14px;
914 font-weight: 600;
915 color: #fff;
916 }
917 #toplevel_page_visualizer ul.wp-submenu li:last-child > a > span {
918 color: #fff !important;
919 }
920 #toplevel_page_visualizer ul.wp-submenu li:last-child > a:hover {
921 box-shadow: inherit;
922 }
923 </style>
924 <?php
925 if ( get_option( 'visualizer_fresh_install', false ) ) {
926 ?>
927 <style type="text/css">
928 #toplevel_page_visualizer ul.wp-submenu li.wp-first-item + li + li + li {
929 display: none;
930 }
931 </style>
932 <?php
933 }
934 }
935 }
936
937 /**
938 * Adds the screen options for pagination.
939 */
940 public function addScreenOptions() {
941 $screen = get_current_screen();
942
943 // bail if it's some other page.
944 if ( ! is_object( $screen ) || $screen->id !== $this->_libraryPage ) {
945 return;
946 }
947
948 $args = array(
949 'label' => __( 'Number of charts per page:', 'visualizer' ),
950 'default' => 6,
951 'option' => 'visualizer_library_per_page',
952 );
953 add_screen_option( 'per_page', $args );
954 }
955
956 /**
957 * Returns the screen option for pagination.
958 */
959 public function setScreenOptions( $status, $option, $value ) {
960 if ( 'visualizer_library_per_page' === $option ) {
961 return $value;
962 }
963 }
964
965 /**
966 * Adds the display filters to be used in the meta_query while displaying the charts.
967 */
968 private function getDisplayFilters( &$query_args ) {
969 $query = array();
970 global $sitepress;
971 if ( Visualizer_Module::is_pro() && ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) ) {
972 $current_lang = icl_get_current_language();
973 if ( in_array( $current_lang, array( 'all', icl_get_default_language() ), true ) ) {
974 $query[] = array(
975 'key' => 'chart_lang',
976 'compare' => 'NOT EXISTS',
977 );
978 } else {
979 $query[] = array(
980 'key' => 'chart_lang',
981 'value' => $current_lang,
982 'compare' => '=',
983 );
984 }
985 }
986
987 // add chart type filter to the query arguments
988 $type = filter_input( INPUT_GET, 'type' );
989 if ( $type && in_array( $type, Visualizer_Plugin::getChartTypes(), true ) ) {
990 $query[] = array(
991 'key' => Visualizer_Plugin::CF_CHART_TYPE,
992 'value' => $type,
993 'compare' => '=',
994 );
995 }
996
997 // add chart library filter to the query arguments
998 $library = filter_input( INPUT_GET, 'library' );
999 if ( $library ) {
1000 $query[] = array(
1001 'key' => Visualizer_Plugin::CF_CHART_LIBRARY,
1002 'value' => $library,
1003 'compare' => '=',
1004 );
1005 }
1006
1007 // add date filter to the query arguments
1008 $date = filter_input( INPUT_GET, 'date' );
1009 $possible = array_keys( Visualizer_Plugin::getSupportedDateFilter() );
1010 if ( $date && in_array( $date, $possible, true ) ) {
1011 $query_args['date_query'] = array(
1012 'after' => "$date -1 day",
1013 'inclusive' => true,
1014 );
1015 }
1016
1017 // add source filter to the query arguments
1018 $source = filter_input( INPUT_GET, 'source' );
1019 if ( $source ) {
1020 $source = ucwords( $source );
1021 $source = "Visualizer_Source_{$source}";
1022 $query[] = array(
1023 'key' => Visualizer_Plugin::CF_SOURCE,
1024 'value' => $source,
1025 'compare' => '=',
1026 );
1027 }
1028
1029 $query_args['meta_query'] = $query;
1030
1031 $orderby = filter_input( INPUT_GET, 'orderby' );
1032 $order = filter_input( INPUT_GET, 'order' );
1033 if ( $orderby ) {
1034 $query_args['order_by'] = $orderby;
1035 }
1036 $query_args['order'] = empty( $order ) ? 'desc' : $order;
1037 }
1038
1039 /**
1040 * Get the instance of WP_Query that fetches the charts as per the given criteria.
1041 */
1042 private function getQuery() {
1043 static $q;
1044 if ( ! is_null( $q ) ) {
1045 return $q;
1046 }
1047
1048 // get current page
1049 $page = filter_input(
1050 INPUT_GET,
1051 'vpage',
1052 FILTER_VALIDATE_INT,
1053 array(
1054 'options' => array(
1055 'min_range' => 1,
1056 'default' => 1,
1057 ),
1058 )
1059 );
1060
1061 $per_page = 6;
1062 $screen = get_current_screen();
1063 if ( $screen ) {
1064 // retrieve the per_page option
1065 $screen_option = $screen->get_option( 'per_page', 'option' );
1066 // retrieve the value stored for the current user
1067 $user = get_current_user_id();
1068 $per_page = get_user_meta( $user, $screen_option, true );
1069 if ( empty( $per_page ) || $per_page < 1 ) {
1070 // nothing set, get the default value
1071 $per_page = $screen->get_option( 'per_page', 'default' );
1072 }
1073 }
1074
1075 // the initial query arguments to fetch charts
1076 $query_args = array(
1077 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1078 'posts_per_page' => $per_page,
1079 'paged' => $page,
1080 );
1081
1082 $this->getDisplayFilters( $query_args );
1083
1084 // Added by Ash/Upwork
1085 $filterByMeta = ! empty( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1086 if ( $filterByMeta ) {
1087 $query = array(
1088 'key' => Visualizer_Plugin::CF_SETTINGS,
1089 'value' => $filterByMeta,
1090 'compare' => 'LIKE',
1091 );
1092 $meta = isset( $query_args['meta_query'] ) ? $query_args['meta_query'] : array();
1093 $meta[] = $query;
1094 $query_args['meta_query'] = $meta;
1095 }
1096 $q = new WP_Query( apply_filters( 'visualizer_query_args', $query_args ) );
1097 return $q;
1098 }
1099
1100 /**
1101 * Renders support page.
1102 *
1103 * @since 3.3.0
1104 *
1105 * @access public
1106 */
1107 public function renderSupportPage() {
1108 wp_enqueue_style( 'visualizer-upsell', VISUALIZER_ABSURL . 'css/upsell.css', array(), Visualizer_Plugin::VERSION );
1109 include_once VISUALIZER_ABSPATH . '/templates/support.php';
1110 }
1111
1112 /**
1113 * Enqueues scripts/styles for the Settings page.
1114 *
1115 * @access public
1116 */
1117 public function enqueueSettingsScripts( string $hook_suffix ): void {
1118 if ( $this->_settingsPage !== $hook_suffix ) {
1119 return;
1120 }
1121 wp_enqueue_style( 'wp-color-picker' );
1122 wp_enqueue_script( 'wp-color-picker' );
1123 }
1124
1125 /**
1126 * Renders the global style settings page.
1127 *
1128 * @access public
1129 */
1130 public function renderSettingsPage(): void {
1131 if ( ! current_user_can( 'manage_options' ) ) {
1132 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'visualizer' ) );
1133 }
1134 $settings = self::getGlobalSettings();
1135 include_once VISUALIZER_ABSPATH . '/templates/global-settings.php';
1136 }
1137
1138 /**
1139 * Returns the global style settings option.
1140 *
1141 * @return array<string, string>
1142 * @access public
1143 * @static
1144 */
1145 public static function getGlobalSettings(): array {
1146 $defaults = array(
1147 'color_primary' => '',
1148 'color_secondary' => '',
1149 'apply_existing' => '0',
1150 );
1151 $saved = get_option( self::OPTION_GLOBAL_SETTINGS, array() );
1152 return wp_parse_args( $saved, $defaults );
1153 }
1154
1155 /**
1156 * Handles saving of the global style settings.
1157 *
1158 * @access public
1159 */
1160 public function saveGlobalSettings(): void {
1161 if ( ! current_user_can( 'manage_options' ) ) {
1162 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'visualizer' ) );
1163 }
1164
1165 check_admin_referer( 'visualizer_save_global_settings' );
1166
1167 $color_primary = sanitize_hex_color( wp_unslash( $_POST['visualizer_color_primary'] ?? '' ) );
1168 $color_secondary = sanitize_hex_color( wp_unslash( $_POST['visualizer_color_secondary'] ?? '' ) );
1169 $apply_existing = ! empty( $_POST['visualizer_apply_existing'] ) ? '1' : '0';
1170 $clear = ! empty( $_POST['visualizer_clear_settings'] );
1171
1172 if ( $clear ) {
1173 delete_option( self::OPTION_GLOBAL_SETTINGS );
1174 } else {
1175 $settings = array(
1176 'color_primary' => $color_primary ? $color_primary : '',
1177 'color_secondary' => $color_secondary ? $color_secondary : '',
1178 'apply_existing' => $apply_existing,
1179 );
1180 update_option( self::OPTION_GLOBAL_SETTINGS, $settings );
1181 }
1182
1183 wp_safe_redirect(
1184 add_query_arg(
1185 array(
1186 'page' => 'viz-settings',
1187 'updated' => $clear ? 'cleared' : 'true',
1188 ),
1189 admin_url( 'admin.php' )
1190 )
1191 );
1192 exit;
1193 }
1194
1195 /**
1196 * Renders visualizer library page.
1197 *
1198 * @since 1.0.0
1199 *
1200 * @access public
1201 */
1202 public function renderLibraryPage() {
1203 $charts = array();
1204 $query = $this->getQuery();
1205
1206 // get current page
1207 $page = filter_input(
1208 INPUT_GET,
1209 'vpage',
1210 FILTER_VALIDATE_INT,
1211 array(
1212 'options' => array(
1213 'min_range' => 1,
1214 'default' => 1,
1215 ),
1216 )
1217 );
1218 // add chart type filter to the query arguments
1219 $filter = filter_input( INPUT_GET, 'type' );
1220 if ( ! ( $filter && in_array( $filter, Visualizer_Plugin::getChartTypes(), true ) ) ) {
1221 $filter = 'all';
1222 }
1223
1224 $css = '';
1225 while ( $query->have_posts() ) {
1226 $chart = $query->next_post();
1227
1228 // 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.
1229 $chart = $this->handleExistingRevisions( $chart->ID, $chart );
1230 // refresh a "live" db query chart.
1231 $chart = apply_filters( 'visualizer_schedule_refresh_chart', $chart, $chart->ID, false );
1232
1233 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
1234
1235 // fetch and update settings
1236 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1237
1238 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
1239 if ( ! empty( $atts['settings'] ) ) {
1240 $settings = apply_filters( $atts['settings'], $settings, $chart->ID, $type );
1241 }
1242
1243 if ( $settings ) {
1244 unset( $settings['height'], $settings['width'], $settings['chartArea'] );
1245 }
1246
1247 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ), $chart->ID, $type );
1248 $data = self::get_chart_data( $chart, $type );
1249
1250 // Set default setting series when add manual chart data - Only during first creation.
1251 if ( isset( $settings['series'] ) && ! empty( $series ) ) {
1252 $count_series = count( $series ) - count( $settings['series'] );
1253 if ( $count_series > 1 ) {
1254 $clone_last_series = end( $settings['series'] );
1255 $clone_last_series = array_fill_keys( array_keys( $clone_last_series ), '' );
1256 foreach ( range( 1, $count_series ) as $item ) {
1257 $settings['series'][] = $clone_last_series;
1258 }
1259 }
1260 }
1261
1262 $library = $this->load_chart_type( $chart->ID );
1263
1264 $id = 'visualizer-' . $chart->ID;
1265 $arguments = $this->get_inline_custom_css( $id, $settings );
1266 if ( ! empty( $arguments ) ) {
1267 $css .= $arguments[0];
1268 $settings = $arguments[1];
1269 }
1270
1271 if ( isset( $settings['controls']['ui']['labelStacking'] ) ) {
1272 unset( $settings['controls']['ui']['labelStacking'] );
1273 }
1274 if ( isset( $settings['controls']['ui']['orientation'] ) ) {
1275 unset( $settings['controls']['ui']['orientation'] );
1276 }
1277
1278 // add chart to the array
1279 $charts[ $id ] = array(
1280 'id' => $chart->ID,
1281 'title' => $chart->post_title,
1282 'type' => $type,
1283 'series' => $series,
1284 'settings' => $settings,
1285 'data' => $data,
1286 'library' => $library,
1287 );
1288 if ( 'd3' === $library ) {
1289 $charts[ $id ]['code'] = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
1290 }
1291 }
1292 // enqueue charts array
1293 $ajaxurl = admin_url( 'admin-ajax.php' );
1294
1295 wp_localize_script(
1296 'visualizer-library',
1297 'visualizer',
1298 array(
1299 'language' => $this->get_language(),
1300 'map_api_key' => get_option( 'visualizer-map-api-key' ),
1301 'charts' => $charts,
1302 'urls' => array(
1303 'base' => esc_url( add_query_arg( array( 'vpage' => false, 'vaction' => false ) ) ),
1304 'create' => esc_url(
1305 add_query_arg(
1306 array(
1307 'action' => Visualizer_Plugin::ACTION_CREATE_CHART,
1308 'library' => 'yes',
1309 'type' => isset( $_GET['type'] ) ? $_GET['type'] : '',
1310 'chart-library' => isset( $_GET['chart-library'] ) ? $_GET['chart-library'] : '',
1311 'vaction' => false,
1312 ),
1313 $ajaxurl
1314 )
1315 ),
1316 'edit' => esc_url(
1317 add_query_arg(
1318 array(
1319 'action' => Visualizer_Plugin::ACTION_EDIT_CHART,
1320 'library' => 'yes',
1321 'vaction' => false,
1322 ),
1323 $ajaxurl
1324 )
1325 ),
1326 ),
1327 'page_type' => 'library',
1328 'is_front' => false,
1329 'i10n' => array(
1330 'copied' => __( 'The shortcode has been copied to your clipboard. Hit Ctrl-V/Cmd-V to paste it.', 'visualizer' ),
1331 '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' ),
1332 ),
1333 'is_pro_user' => Visualizer_Module::is_pro(),
1334 )
1335 );
1336 // render library page
1337 $render = new Visualizer_Render_Library();
1338 $render->charts = $charts;
1339 $render->type = $filter;
1340 $render->types = self::_getChartTypesLocalized( false, false, false, true );
1341 $render->custom_css = $css;
1342 $render->pagination = paginate_links(
1343 array(
1344 'base' => add_query_arg( array( 'vpage' => '%#%', 'vaction' => false ) ),
1345 'format' => '',
1346 'current' => $page,
1347 'total' => $query->max_num_pages,
1348 'type' => 'array',
1349 )
1350 );
1351
1352 $render->render();
1353 }
1354
1355 /**
1356 * Updates the plugin's action links, which will be rendered at the plugins table.
1357 *
1358 * @since 1.0.0
1359 *
1360 * @access public
1361 *
1362 * @param array $links The array of original action links.
1363 * @param string $file The plugin basename.
1364 *
1365 * @return array Updated array of action links.
1366 */
1367 public function getPluginActionLinks( $links, $file ) {
1368 if ( $file === plugin_basename( VISUALIZER_BASEFILE ) ) {
1369 array_unshift(
1370 $links,
1371 sprintf(
1372 '<a href="%s">%s</a>',
1373 admin_url( 'admin.php?page=' . Visualizer_Plugin::NAME ),
1374 esc_html__( 'Library', 'visualizer' )
1375 )
1376 );
1377 }
1378
1379 return $links;
1380 }
1381
1382 /**
1383 * Updates the plugin's meta links, which will be rendered at the plugins table.
1384 *
1385 * @since 1.0.0
1386 *
1387 * @access public
1388 *
1389 * @param array $plugin_meta The array of a plugin meta links.
1390 * @param string $plugin_file The plugin's basename.
1391 *
1392 * @return array Updated array of plugin meta links.
1393 */
1394 public function getPluginMetaLinks( $plugin_meta, $plugin_file ) {
1395 if ( Visualizer_Module::is_pro() ) {
1396 return $plugin_meta;
1397 }
1398
1399 // Also suppress the upsell when Pro is installed but not currently active.
1400 if ( ! function_exists( 'get_plugins' ) ) {
1401 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1402 }
1403 foreach ( array_keys( get_plugins() ) as $installed_plugin ) {
1404 if ( false !== strpos( $installed_plugin, 'visualizer-pro' ) ) {
1405 return $plugin_meta;
1406 }
1407 }
1408
1409 if ( $plugin_file === plugin_basename( VISUALIZER_BASEFILE ) ) {
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 // flattr link
1416 $plugin_meta[] = sprintf(
1417 '<a style="color:red" href="' . tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'pluginrow' ) . '" target="_blank">%s</a>',
1418 esc_html__( 'Get Visualizer Pro', 'visualizer' )
1419 );
1420 }
1421
1422 return $plugin_meta;
1423 }
1424
1425 /**
1426 * Returns true when premium chart types should be enabled for the current user.
1427 *
1428 * Pro 1.9.0+ hooks the 'visualizer_is_pro' filter and returns true only when
1429 * the license is valid. Pre-1.9.0 Pro defined a Visualizer_Pro class instead;
1430 * we detect that as a fallback so those legacy installs still work.
1431 *
1432 * Passing false as the filter default ensures the constant VISUALIZER_PRO
1433 * (which is set to true whenever the Pro class exists, regardless of license
1434 * state) cannot bypass the license check.
1435 *
1436 * @return bool
1437 */
1438 public static function proFeaturesEnabled() {
1439 $is_pro_filter = apply_filters( 'visualizer_is_pro', false );
1440
1441 // Pro 1.9.0+: filter is hooked and returns true only with a valid license.
1442 if ( $is_pro_filter ) {
1443 return true;
1444 }
1445
1446 // Pro is installed (active) but the license check above did not pass.
1447 // Do NOT fall through to the legacy "old user" path — that path exists
1448 // only for sites that never had Pro installed. If Pro is present but
1449 // the license is inactive we should lock, regardless of chart history.
1450 if ( class_exists( 'Visualizer_Pro', false ) ) {
1451 return false;
1452 }
1453
1454 // No Pro installed at all: grant legacy access to existing users so
1455 // their charts are not suddenly broken when they upgrade the free plugin.
1456 return 'yes' === get_option( 'visualizer-new-user' ) ? false : true;
1457 }
1458
1459 /**
1460 * Multilingual Support.
1461 *
1462 * @param int $chart_id Chart ID.
1463 * @return bool Default false
1464 */
1465 public function addMultilingualSupport( $chart_id ) {
1466 global $sitepress;
1467 if ( Visualizer_Module::is_pro() ) {
1468 return;
1469 }
1470 if ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) {
1471 $language = icl_get_languages();
1472 $current_lang = icl_get_current_language();
1473 $default_lang = icl_get_default_language();
1474 $post_info = wpml_get_language_information( null, $chart_id );
1475 $translations = array();
1476 if ( ! empty( $post_info ) && ( $default_lang === $post_info['language_code'] ) ) {
1477 $trid = $sitepress->get_element_trid( $chart_id, 'post_' . Visualizer_Plugin::CPT_VISUALIZER );
1478 $translations = $sitepress->get_element_translations( $trid );
1479 }
1480 if ( empty( $translations ) ) {
1481 return;
1482 }
1483 ?>
1484 <hr><div class="visualizer-languages-list only-pro">
1485 <?php
1486 foreach ( $language as $lang ) {
1487 $lang_code = $lang['code'];
1488 if ( $current_lang !== $lang_code ) {
1489 ?>
1490 <a href="javascript:;">
1491 <img src="<?php echo esc_url( $lang['country_flag_url'] ); ?>" alt="<?php echo esc_attr( $lang['translated_name'] ); ?>">
1492 </a>
1493 <?php
1494 }
1495 }
1496 ?>
1497 <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>
1498 </div>
1499 <?php
1500 }
1501 }
1502
1503 /**
1504 * Check chart is enabled or not.
1505 *
1506 * @param string $type Chart ID.
1507 *
1508 * @return bool Default false
1509 */
1510 public static function checkChartStatus( $type ) {
1511 $types = self::_getChartTypesLocalized( false, false, false, true );
1512 if ( isset( $types[ $type ] ) ) {
1513 return ! empty( $types[ $type ]['enabled'] );
1514 }
1515 return false;
1516 }
1517
1518 /**
1519 * Get the survey metadata.
1520 *
1521 * @param array $data The data for survey in Formbricks format.
1522 * @param string $page_slug The slug of the loaded page.
1523 *
1524 * @return array The survey metadata.
1525 */
1526 public function get_survey_metadata( $data, $page_slug ) {
1527 $install_date = get_option( 'visualizer_install', time() );
1528 $install_days_number = intval( ( time() - $install_date ) / DAY_IN_SECONDS );
1529
1530 $license_status = apply_filters( 'product_visualizer_license_status', 'invalid' );
1531 $license_plan = apply_filters( 'product_visualizer_license_plan', false );
1532 $license_key = apply_filters( 'product_visualizer_license_key', false );
1533
1534 $plugin_data = get_plugin_data( VISUALIZER_BASEFILE, false, false );
1535 $plugin_version = '';
1536
1537 if ( ! empty( $plugin_data['Version'] ) ) {
1538 $plugin_version = $plugin_data['Version'];
1539 }
1540
1541 $count_charts_cache_key = 'visualizer_count_charts';
1542 $charts_number = get_transient( $count_charts_cache_key );
1543
1544 if ( false === $charts_number ) {
1545 $charts_number = $this->count_charts( 100 );
1546 set_transient( $count_charts_cache_key, $charts_number, 100 === $charts_number ? WEEK_IN_SECONDS : 6 * HOUR_IN_SECONDS );
1547 } else {
1548 $charts_number = strval( $charts_number );
1549 }
1550
1551 $data = array(
1552 'environmentId' => 'cltef8cut1s7wyyfxy3rlxzs5',
1553 'attributes' => array(
1554 'free_version' => $plugin_version,
1555 'pro_version' => defined( 'VISUALIZER_PRO_VERSION' ) ? VISUALIZER_PRO_VERSION : '',
1556 'license_status' => $license_status,
1557 'install_days_number' => $install_days_number,
1558 'charts_number' => $charts_number,
1559 ),
1560 );
1561
1562 if ( ! empty( $license_plan ) ) {
1563 $data['attributes']['plan'] = $license_plan;
1564 }
1565
1566 if ( ! empty( $license_key ) ) {
1567 $data['attributes']['license_key'] = apply_filters( 'themeisle_sdk_secret_masking', $license_key );
1568 }
1569
1570 return $data;
1571 }
1572
1573 /**
1574 * Count the charts.
1575 *
1576 * @param int $limit The count limit (optional).
1577 *
1578 * @return int The number of charts.
1579 */
1580 public function count_charts( $limit = -1 ) {
1581 $args = array(
1582 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1583 'post_status' => 'publish',
1584 'posts_per_page' => $limit,
1585 'fields' => 'ids',
1586 );
1587
1588 $query = new WP_Query( $args);
1589 return $query->post_count;
1590 }
1591
1592 /**
1593 * Set the black friday data.
1594 *
1595 * @param array $configs The configuration array for the loaded products.
1596 * @return array
1597 */
1598 public function add_black_friday_data( $configs ) {
1599 $config = $configs['default'];
1600
1601 // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name.
1602 $message_template = __( 'Our biggest sale of the year: %1$sup to %2$s OFF%3$s on %4$s. Don\'t miss this limited-time offer.', 'visualizer' );
1603 $product_label = 'Visualizer';
1604 $discount = '70%';
1605
1606 $plan = apply_filters( 'product_visualizer_license_plan', 0 );
1607 $license = apply_filters( 'product_visualizer_license_key', false );
1608 $is_pro = 0 < $plan;
1609
1610 if ( $is_pro ) {
1611 // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name.
1612 $message_template = __( 'Get %1$sup to %2$s off%3$s when you upgrade your %4$s plan or renew early.', 'visualizer' );
1613 $product_label = 'Visualizer Pro';
1614 $discount = '30%';
1615 }
1616
1617 $product_label = sprintf( '<strong>%s</strong>', $product_label );
1618 $url_params = array(
1619 'utm_term' => $is_pro ? 'plan-' . $plan : 'free',
1620 'lkey' => ! empty( $license ) ? $license : false,
1621 );
1622
1623 $config['message'] = sprintf( $message_template, '<strong>', $discount, '</strong>', $product_label );
1624 $config['sale_url'] = add_query_arg(
1625 $url_params,
1626 tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/vizualizer-bf', 'bfcm', 'visualizer' ) )
1627 );
1628
1629 $configs[ VISUALIZER_DIRNAME ] = $config;
1630
1631 return $configs;
1632 }
1633 }
1634