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
← All changes | classes/Visualizer/Module/Admin.php +280 -17 3.11.154.0.0 View file →
@@ -29,8 +29,10 @@
29 29 class Visualizer_Module_Admin extends Visualizer_Module {
30 30
31 31 const NAME = __CLASS__;
32 32
33 + const OPTION_GLOBAL_SETTINGS = 'visualizer_global_settings';
34 +
33 35 /**
34 36 * Library page suffix.
35 37 *
36 38 * @since 1.0.0
@@ -48,8 +50,16 @@
48 50 */
49 51 private $_supportPage;
50 52
51 53 /**
54 + * Settings page suffix.
55 + *
56 + * @access private
57 + * @var string
58 + */
59 + private $_settingsPage;
60 +
61 + /**
52 62 * Constructor.
53 63 *
54 64 * @since 1.0.0
55 65 *
@@ -64,8 +74,10 @@
64 74 $this->_addAction( 'enqueue_block_editor_assets', 'enqueueMediaScripts' );
65 75 $this->_addAction( 'admin_footer', 'renderTemplates' );
66 76 $this->_addAction( 'admin_enqueue_scripts', 'enqueueLibraryScripts', null, 0 );
67 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' );
68 80 $this->_addAction( 'admin_menu', 'registerAdminMenu' );
69 81 $this->_addFilter( 'media_view_strings', 'setupMediaViewStrings' );
70 82 $this->_addFilter( 'plugin_action_links', 'getPluginActionLinks', 10, 2 );
71 83 $this->_addFilter( 'plugin_row_meta', 'getPluginMetaLinks', 10, 2 );
@@ -95,9 +107,8 @@
95 107
96 108 if ( defined( 'TI_E2E_TESTING' ) ) {
97 109 $this->load_cypress_hooks();
98 110 }
99 -
100 111 }
101 112 /**
102 113 * Display review notice.
103 114 */
@@ -103,9 +114,9 @@
103 114 */
104 115 public function render_review_notice( $footer_text ) {
105 116 $current_screen = get_current_screen();
106 117
107 - $visualizer_page_ids = ['toplevel_page_visualizer', 'visualizer_page_viz-support', 'visualizer_page_ti-about-visualizer' ];
118 + $visualizer_page_ids = array( 'toplevel_page_visualizer', 'visualizer_page_viz-support', 'visualizer_page_ti-about-visualizer' );
108 119
109 120 if ( ! empty( $current_screen ) && isset( $current_screen->id ) ) {
110 121 foreach ( $visualizer_page_ids as $page_to_check ) {
111 122 if ( strpos( $current_screen->id, $page_to_check ) !== false ) {
@@ -113,9 +124,9 @@
113 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.
114 125 __( 'Enjoying %1$s? %2$s %3$s rating. Thank you for being so supportive!', 'visualizer' ),
115 126 '<b>Visualizer</b>',
116 127 esc_html__( 'You can help us by leaving a', 'visualizer' ),
117 - '<a href="https://wordpress.org/support/plugin/visualizer/reviews/" target="_blank">&#9733;&#9733;&#9733;&#9733;&#9733;</a>'
128 + '<a href="https://wordpress.org/support/plugin/visualizer/reviews/#new-post" target="_blank">&#9733;&#9733;&#9733;&#9733;&#9733;</a>'
118 129 );
119 130 break;
120 131 }
121 132 }
@@ -133,9 +144,9 @@
133 144 */
134 145 private function load_cypress_hooks() {
135 146 // all charts should load on the same page without pagination.
136 147 add_filter(
137 - 'visualizer_query_args', function( $args ) {
148 + 'visualizer_query_args', function ( $args ) {
138 149 $args['posts_per_page'] = 20;
139 150 return $args;
140 151 }, 10, 1
141 152 );
@@ -252,10 +263,9 @@
252 263 *
253 264 * @access public
254 265 */
255 266 public function init() {
256 - // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
257 - if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' == get_user_option( 'rich_editing' ) ) {
267 + if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' === get_user_option( 'rich_editing' ) ) {
258 268 $this->_addFilter( 'mce_external_languages', 'add_tinymce_lang', 10, 1 );
259 269 $this->_addFilter( 'mce_external_plugins', 'tinymce_plugin', 10, 1 );
260 270 $this->_addFilter( 'mce_buttons', 'register_mce_button', 10, 1 );
261 271 $this->_addFilter( 'tiny_mce_before_init', 'get_strings_for_block', 10, 1 );
@@ -267,9 +277,9 @@
267 277 *
268 278 * @since ?
269 279 * @access friendly
270 280 */
271 - function get_strings_for_block( $settings ) {
281 + public function get_strings_for_block( $settings ) {
272 282 $class = new Visualizer_Module_Language();
273 283 $strings = $class->get_strings();
274 284 $array = array( 'visualizer_tinymce_plugin' => json_encode( $strings ) );
275 285 return array_merge( $settings, $array );
@@ -350,8 +360,31 @@
350 360
351 361 if ( post_type_supports( $typenow, 'editor' ) || $current_screen->id === 'widgets' || $current_screen->id === 'customize' ) {
352 362 wp_enqueue_style( 'visualizer-media', VISUALIZER_ABSURL . 'css/media.css', array( 'media-views' ), Visualizer_Plugin::VERSION );
353 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 +
354 387 // Load all the assets for the different libraries we support.
355 388 $deps = array(
356 389 Visualizer_Render_Sidebar_Google::enqueue_assets( array( 'media-editor' ) ),
357 390 Visualizer_Render_Sidebar_Type_DataTable_Tabular::enqueue_assets( array( 'media-editor' ) ),
@@ -365,9 +398,9 @@
365 398 'visualizer-media-view',
366 399 'visualizer',
367 400 array(
368 401 'i10n' => array(
369 - 'insert' => __( 'Insert', 'visualizer' ),
402 + 'insert' => __( 'Insert Chart', 'visualizer' ),
370 403 ),
371 404 )
372 405 );
373 406 wp_enqueue_script( 'visualizer-media-toolbar', VISUALIZER_ABSURL . 'js/media/toolbar.js', array( 'visualizer-media-view' ), Visualizer_Plugin::VERSION, true );
@@ -396,9 +429,9 @@
396 429 'controller' => array(
397 430 'title' => esc_html__( 'Visualizations', 'visualizer' ),
398 431 ),
399 432 'routers' => array(
400 - 'library' => esc_html__( 'From Library', 'visualizer' ),
433 + 'library' => esc_html__( 'Insert from Library', 'visualizer' ),
401 434 'create' => esc_html__( 'Create New', 'visualizer' ),
402 435 ),
403 436 'library' => array(
404 437 'filters' => $chart_types,
@@ -428,9 +461,9 @@
428 461 'enabled' => true,
429 462 );
430 463 }
431 464
432 - $enabled = self::proFeaturesLocked();
465 + $enabled = self::proFeaturesEnabled();
433 466
434 467 $types = array_merge(
435 468 $additional,
436 469 array(
@@ -670,8 +703,9 @@
670 703 );
671 704
672 705 wp_enqueue_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
673 706
707 + $has_d3 = false;
674 708 $query = $this->getQuery();
675 709 while ( $query->have_posts() ) {
676 710 $chart = $query->next_post();
677 711 $library = $this->load_chart_type( $chart->ID );
@@ -684,10 +718,107 @@
684 718 apply_filters( 'visualizer_assets_render', array( 'visualizer-library', 'visualizer-customization' ), true ),
685 719 Visualizer_Plugin::VERSION,
686 720 true
687 721 );
722 + if ( 'd3' === $library ) {
723 + $has_d3 = true;
724 + }
688 725 }
689 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 +
690 821 do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'library' );
691 822 }
692 823
693 824 /**
@@ -745,8 +876,17 @@
745 876 'edit_posts',
746 877 'admin.php?page=' . Visualizer_Plugin::NAME . '&vaction=addnew'
747 878 );
748 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 +
749 889 $this->_supportPage = add_submenu_page(
750 890 Visualizer_Plugin::NAME,
751 891 __( 'Support', 'visualizer' ),
752 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>',
@@ -796,9 +936,9 @@
796 936
797 937 /**
798 938 * Adds the screen options for pagination.
799 939 */
800 - function addScreenOptions() {
940 + public function addScreenOptions() {
801 941 $screen = get_current_screen();
802 942
803 943 // bail if it's some other page.
804 944 if ( ! is_object( $screen ) || $screen->id !== $this->_libraryPage ) {
@@ -815,9 +955,9 @@
815 955
816 956 /**
817 957 * Returns the screen option for pagination.
818 958 */
819 - function setScreenOptions( $status, $option, $value ) {
959 + public function setScreenOptions( $status, $option, $value ) {
820 960 if ( 'visualizer_library_per_page' === $option ) {
821 961 return $value;
822 962 }
823 963 }
@@ -969,8 +1109,91 @@
969 1109 include_once VISUALIZER_ABSPATH . '/templates/support.php';
970 1110 }
971 1111
972 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 + /**
973 1196 * Renders visualizer library page.
974 1197 *
975 1198 * @since 1.0.0
976 1199 *
@@ -1054,8 +1277,9 @@
1054 1277
1055 1278 // add chart to the array
1056 1279 $charts[ $id ] = array(
1057 1280 'id' => $chart->ID,
1281 + 'title' => $chart->post_title,
1058 1282 'type' => $type,
1059 1283 'series' => $series,
1060 1284 'settings' => $settings,
1061 1285 'data' => $data,
@@ -1060,8 +1284,11 @@
1060 1284 'settings' => $settings,
1061 1285 'data' => $data,
1062 1286 'library' => $library,
1063 1287 );
1288 + if ( 'd3' === $library ) {
1289 + $charts[ $id ]['code'] = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
1290 + }
1064 1291 }
1065 1292 // enqueue charts array
1066 1293 $ajaxurl = admin_url( 'admin-ajax.php' );
1067 1294
@@ -1164,8 +1391,22 @@
1164 1391 *
1165 1392 * @return array Updated array of plugin meta links.
1166 1393 */
1167 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 +
1168 1409 if ( $plugin_file === plugin_basename( VISUALIZER_BASEFILE ) ) {
1169 1410 // knowledge base link
1170 1411 $plugin_meta[] = sprintf(
1171 1412 '<a href="' . VISUALIZER_MAIN_DOC . '" target="_blank">%s</a>',
@@ -1181,17 +1422,39 @@
1181 1422 return $plugin_meta;
1182 1423 }
1183 1424
1184 1425 /**
1185 - * If check is existing user.
1426 + * Returns true when premium chart types should be enabled for the current user.
1186 1427 *
1187 - * @return bool Default false
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
1188 1437 */
1189 - public static function proFeaturesLocked() {
1190 - if ( Visualizer_Module::is_pro() ) {
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 ) {
1191 1443 return true;
1192 1444 }
1193 - return 'yes' === get_option( 'visualizer-new-user', 'yes' ) ? false : true;
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;
1194 1457 }
1195 1458
1196 1459 /**
1197 1460 * Multilingual Support.