PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.1
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.1
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/Chart.php +202 -66 3.10.34.0.1 View file →
@@ -69,9 +69,8 @@
69 69
70 70 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY, 'saveFilter' );
71 71
72 72 $this->_addFilter( 'visualizer_get_sidebar', 'getSidebar', 10, 2 );
73 -
74 73 }
75 74
76 75 /**
77 76 * Generates the HTML of the sidebar for the chart.
@@ -374,13 +373,13 @@
374 373 * @since 1.0.0
375 374 *
376 375 * @access private
377 376 *
378 - * @param WP_Post $chart The chart object.
377 + * @param WP_Post|null $chart The chart object.
379 378 *
380 379 * @return array The array of chart data.
381 380 */
382 - private function _getChartArray( WP_Post $chart = null ) {
381 + private function _getChartArray( $chart = null ) {
383 382 if ( is_null( $chart ) ) {
384 383 $chart = $this->_chart;
385 384 }
386 385 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
@@ -402,8 +401,13 @@
402 401 }
403 402
404 403 $date_formats = Visualizer_Source::get_date_formats_if_exists( $series, $data );
405 404
405 + $code = '';
406 + if ( 'd3' === $library ) {
407 + $code = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
408 + }
409 +
406 410 return array(
407 411 'type' => $type,
408 412 'series' => $series,
409 413 'settings' => $settings,
@@ -408,8 +412,9 @@
408 412 'series' => $series,
409 413 'settings' => $settings,
410 414 'data' => $data,
411 415 'library' => $library,
416 + 'code' => $code,
412 417 'css' => $css,
413 418 'date_formats' => $date_formats,
414 419 );
415 420 }
@@ -523,8 +528,12 @@
523 528 *
524 529 * @access public
525 530 */
526 531 public function renderChartPages() {
532 + if ( ! current_user_can( 'edit_posts' ) ) {
533 + wp_die( __( 'You do not have permission to access this page.', 'visualizer' ) );
534 + }
535 +
527 536 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
528 537 if ( ! defined( 'ET_BUILDER_PRODUCT_VERSION' ) && function_exists( 'et_get_theme_version' ) ) {
529 538 define( 'ET_BUILDER_PRODUCT_VERSION', et_get_theme_version() );
530 539 }
@@ -534,9 +543,10 @@
534 543 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
535 544 if ( ! empty( $_POST ) ) {
536 545 $_POST = map_deep( $_POST, 'wp_strip_all_tags' );
537 546 }
538 - if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
547 + $chart = $chart_id ? get_post( $chart_id ) : null;
548 + if ( ! $chart_id || ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
539 549 if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
540 550 $this->deleteOldCharts();
541 551 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
542 552 $chart_status = Visualizer_Module_Admin::checkChartStatus( $default_type );
@@ -570,35 +580,33 @@
570 580
571 581 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
572 582 }
573 583 } else {
574 - if ( current_user_can( 'edit_posts' ) ) {
575 - $parent_chart_id = isset( $_GET['parent_chart_id'] ) ? filter_var( $_GET['parent_chart_id'], FILTER_VALIDATE_INT ) : '';
576 - $success = false;
577 - if ( $parent_chart_id ) {
578 - $parent_chart = get_post( $parent_chart_id );
579 - $success = $parent_chart && $parent_chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
580 - }
581 - if ( $success ) {
582 - $new_chart_id = wp_insert_post(
583 - array(
584 - 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
585 - 'post_title' => 'Visualization',
586 - 'post_author' => get_current_user_id(),
587 - 'post_status' => $parent_chart->post_status,
588 - 'post_content' => $parent_chart->post_content,
589 - )
590 - );
584 + $parent_chart_id = filter_var( $_GET['parent_chart_id'], FILTER_VALIDATE_INT );
585 + $success = false;
586 + if ( $parent_chart_id ) {
587 + $parent_chart = get_post( $parent_chart_id );
588 + $success = $parent_chart && $parent_chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
589 + }
590 + if ( $success ) {
591 + $new_chart_id = wp_insert_post(
592 + array(
593 + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
594 + 'post_title' => 'Visualization',
595 + 'post_author' => get_current_user_id(),
596 + 'post_status' => $parent_chart->post_status,
597 + 'post_content' => $parent_chart->post_content,
598 + )
599 + );
591 600
592 - if ( is_wp_error( $new_chart_id ) ) {
593 - do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while cloning chart %d = %s', $parent_chart_id, print_r( $new_chart_id, true ) ), 'error', __FILE__, __LINE__ );
594 - } else {
595 - $post_meta = get_post_meta( $parent_chart_id );
596 - $chart_id = $new_chart_id;
597 - foreach ( $post_meta as $key => $value ) {
598 - if ( strpos( $key, 'visualizer-' ) !== false ) {
599 - add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
600 - }
601 + if ( is_wp_error( $new_chart_id ) ) {
602 + do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while cloning chart %d = %s', $parent_chart_id, print_r( $new_chart_id, true ) ), 'error', __FILE__, __LINE__ );
603 + } else {
604 + $post_meta = get_post_meta( $parent_chart_id );
605 + $chart_id = $new_chart_id;
606 + foreach ( $post_meta as $key => $value ) {
607 + if ( strpos( $key, 'visualizer-' ) !== false ) {
608 + add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
601 609 }
602 610 }
603 611 }
604 612 }
@@ -735,9 +743,9 @@
735 743 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
736 744 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
737 745 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
738 746 wp_register_script( 'visualizer-codemirror-sql-hint', '//codemirror.net/addon/hint/sql-hint.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
739 - wp_register_script( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.js', array( 'visualizer-codemirror-sql', 'visualizer-codemirror-sql-hint', 'visualizer-codemirror-placeholder', 'visualizer-codemirror-matchbrackets', 'visualizer-codemirror-closebrackets' ), Visualizer_Plugin::VERSION );
747 + wp_register_script( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.js', array( 'visualizer-codemirror-sql', 'visualizer-codemirror-sql-hint', 'visualizer-codemirror-placeholder', 'visualizer-codemirror-matchbrackets', 'visualizer-codemirror-closebrackets' ), Visualizer_Plugin::VERSION );
740 748 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
741 749 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
742 750
743 751 wp_enqueue_script( 'visualizer-codemirror-hint' );
@@ -751,9 +759,9 @@
751 759 'lineWrapping' => true,
752 760 'dragDrop' => false,
753 761 'matchBrackets' => true,
754 762 'autoCloseBrackets' => true,
755 - 'extraKeys' => array( 'Ctrl-Space' => 'autocomplete' ),
763 + 'extraKeys' => array( 'Shift-Space' => 'autocomplete' ),
756 764 'hintOptions' => array( 'tables' => $table_col_mapping ),
757 765 ),
758 766 )
759 767 );
@@ -787,9 +795,12 @@
787 795 update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
788 796 }
789 797
790 798 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
791 - if ( $this->_chart->post_status === 'auto-draft' ) {
799 + $is_canceled = isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] );
800 + $is_newly_created = $this->_chart->post_status === 'auto-draft';
801 +
802 + if ( $is_newly_created && ! $is_canceled ) {
792 803 $this->_chart->post_status = 'publish';
793 804
794 805 // ensure that a revision is not created. If a revision is created it will have the proper data and the parent of the revision will have default data.
795 806 // we do not want any difference in data so disable revisions temporarily.
@@ -797,10 +808,15 @@
797 808
798 809 wp_update_post( $this->_chart->to_array() );
799 810 }
800 811 // save meta data only when it is NOT being canceled.
801 - if ( ! ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) ) {
802 - update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $_POST );
812 + if ( ! $is_canceled ) {
813 + $post_settings = $_POST;
814 + $existing = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
815 + if ( isset( $existing['colors'] ) && is_array( $existing['colors'] ) && ! isset( $post_settings['colors'] ) ) {
816 + $post_settings['colors'] = $existing['colors'];
817 + }
818 + update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $post_settings );
803 819
804 820 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
805 821 // this will help in searching with the chart id.
806 822 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
@@ -847,8 +863,9 @@
847 863 wp_enqueue_style( 'visualizer-frame' );
848 864 wp_enqueue_script( 'visualizer-preview' );
849 865 wp_enqueue_script( 'visualizer-chosen' );
850 866 wp_enqueue_script( 'visualizer-render' );
867 + wp_enqueue_code_editor( array( 'type' => 'application/json' ) );
851 868
852 869 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
853 870 wp_enqueue_script( 'visualizer-editor-simple' );
854 871 wp_localize_script(
@@ -856,12 +873,10 @@
856 873 'visualizer1',
857 874 array(
858 875 'ajax' => array(
859 876 'url' => admin_url( 'admin-ajax.php' ),
860 - 'nonces' => array(
861 - ),
862 - 'actions' => array(
863 - ),
877 + 'nonces' => array(),
878 + 'actions' => array(),
864 879 ),
865 880 )
866 881 );
867 882 }
@@ -872,13 +887,15 @@
872 887 'visualizer-render',
873 888 'visualizer',
874 889 array(
875 890 'l10n' => array(
876 - 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
877 - 'loading' => esc_html__( 'Loading...', 'visualizer' ),
878 - 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
879 - 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
880 - 'save_settings' => __( 'You have modified the chart\'s settings. To modify the source/data again, you must save this chart and reopen it for editing. If you continue without saving the chart, you may lose your changes.', 'visualizer' ),
891 + 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
892 + 'loading' => esc_html__( 'Loading...', 'visualizer' ),
893 + 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
894 + 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
895 + 'save_settings' => __( 'You have modified the chart\'s settings. To modify the source/data again, you must save this chart and reopen it for editing. If you continue without saving the chart, you may lose your changes.', 'visualizer' ),
896 + 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
897 + 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. Use the Manual Configuration option instead.', 'visualizer' ),
881 898 ),
882 899 'charts' => array(
883 900 'canvas' => $data,
884 901 'id' => $this->_chart->ID,
@@ -922,11 +939,10 @@
922 939 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
923 940 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
924 941 ? esc_html__( 'Save Chart', 'visualizer' )
925 942 : esc_html__( 'Create Chart', 'visualizer' );
926 - if ( filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART ) {
927 - $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
928 - }
943 +
944 + $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
929 945 } else {
930 946 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
931 947 }
932 948
@@ -949,9 +965,9 @@
949 965 * @access private
950 966 */
951 967 private function _handleTypesPage() {
952 968 // process post request
953 - if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
969 + if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ), 'visualizer-upload-data' ) ) {
954 970 $type = filter_input( INPUT_POST, 'type' );
955 971 $library = filter_input( INPUT_POST, 'chart-library' );
956 972 if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
957 973 if ( empty( $library ) ) {
@@ -1006,8 +1022,80 @@
1006 1022 * Processes the CSV that is sent in the request as a string.
1007 1023 *
1008 1024 * @since 3.2.0
1009 1025 */
1026 + /**
1027 + * Determines whether a remote URL serves an XLSX file.
1028 + *
1029 + * Used as a fallback when the URL path has no recognisable file extension
1030 + * (e.g. SharePoint, signed S3 URLs, or "download?id=…" endpoints).
1031 + *
1032 + * Uses wp_safe_remote_get() to block requests to private/loopback addresses,
1033 + * and streams the response to a temp file so no body data is held in memory
1034 + * regardless of whether the server honours the Range header.
1035 + *
1036 + * The check relies on the ZIP magic number (PK\x03\x04) that every XLSX
1037 + * file begins with, making it immune to misleading Content-Type headers
1038 + * such as application/octet-stream. Content-Type is used as a last-resort
1039 + * fallback only when the temp file is empty (e.g. a HEAD-only server).
1040 + *
1041 + * @access private
1042 + * @param string $url The remote URL to probe.
1043 + * @return bool TRUE if the file appears to be XLSX, FALSE otherwise.
1044 + */
1045 + private static function _url_is_xlsx( $url ) {
1046 + $tmpfile = wp_tempnam( 'visualizer_xlsx_probe' );
1047 + if ( ! $tmpfile ) {
1048 + return false;
1049 + }
1050 +
1051 + $response = wp_safe_remote_get(
1052 + $url,
1053 + array(
1054 + 'timeout' => 10,
1055 + 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ),
1056 + 'headers' => array( 'Range' => 'bytes=0-3' ),
1057 + 'stream' => true,
1058 + 'filename' => $tmpfile,
1059 + )
1060 + );
1061 +
1062 + if ( is_wp_error( $response ) ) {
1063 + @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1064 + return false;
1065 + }
1066 +
1067 + $magic = '';
1068 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1069 + $fh = @fopen( $tmpfile, 'rb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1070 + if ( $fh ) {
1071 + $magic = fread( $fh, 4 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
1072 + fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1073 + }
1074 + @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1075 +
1076 + if ( strlen( $magic ) >= 4 ) {
1077 + // XLSX (and all ZIP-based Office formats) start with PK\x03\x04.
1078 + return $magic === "PK\x03\x04";
1079 + }
1080 +
1081 + // Last resort: server returned an empty body (e.g. ignored Range and
1082 + // returned only headers). Check Content-Type from the same response.
1083 + // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1084 + return false !== strpos(
1085 + wp_remote_retrieve_header( $response, 'content-type' ),
1086 + 'spreadsheetml'
1087 + );
1088 + }
1089 +
1090 + /**
1091 + * Parses a raw CSV string or editor payload and returns a source object.
1092 + *
1093 + * @access private
1094 + * @param string $data The raw CSV data string.
1095 + * @param string $editor_type The editor type ('text' or 'tabular').
1096 + * @return Visualizer_Source|null The populated source object, or null on failure.
1097 + */
1010 1098 private function handleCSVasString( $data, $editor_type ) {
1011 1099 $source = null;
1012 1100
1013 1101 switch ( $editor_type ) {
@@ -1022,9 +1110,9 @@
1022 1110 continue;
1023 1111 }
1024 1112 $row = explode( ',', $row );
1025 1113 $row = array_map(
1026 - function( $r ) {
1114 + function ( $r ) {
1027 1115 return '' === $r ? ' ' : $r;
1028 1116 },
1029 1117 $row
1030 1118 );
@@ -1073,9 +1161,9 @@
1073 1161 foreach ( $types as $type ) {
1074 1162 if ( empty( $type ) ) {
1075 1163 $exclude[] = $index;
1076 1164 }
1077 - $index++;
1165 + ++$index;
1078 1166 }
1079 1167
1080 1168 // when N headers are being renamed, the number of headers increases by N
1081 1169 // because of the way datatable duplicates header information
@@ -1135,9 +1223,13 @@
1135 1223 // otherwise, assume this is a normal web request.
1136 1224 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1137 1225
1138 1226 // validate nonce
1139 - if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1227 + if (
1228 + ! isset( $_GET['nonce'] ) ||
1229 + ! wp_verify_nonce( $_GET['nonce'], 'visualizer-upload-data' ) ||
1230 + ! current_user_can( 'edit_posts' )
1231 + ) {
1140 1232 if ( ! $can_die ) {
1141 1233 return;
1142 1234 }
1143 1235 status_header( 403 );
@@ -1146,9 +1238,15 @@
1146 1238
1147 1239 // check chart, if chart exists
1148 1240 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1149 1241 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1150 - if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
1242 + $chart = $chart_id ? get_post( $chart_id ) : null;
1243 + if (
1244 + ! $chart_id ||
1245 + ! $chart ||
1246 + $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ||
1247 + ! current_user_can( 'edit_post', $chart_id )
1248 + ) {
1151 1249 if ( ! $can_die ) {
1152 1250 return;
1153 1251 }
1154 1252 status_header( 400 );
@@ -1195,15 +1293,24 @@
1195 1293 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1196 1294 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1197 1295 }
1198 1296 if ( false !== $remote_data ) {
1199 - $source = new Visualizer_Source_Csv_Remote( $remote_data );
1297 + $remote_ext = strtolower( pathinfo( parse_url( $remote_data, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
1298 + if ( 'xlsx' === $remote_ext || ( 'csv' !== $remote_ext && self::_url_is_xlsx( $remote_data ) ) ) {
1299 + $source = new Visualizer_Source_Xlsx_Remote( $remote_data );
1300 + } else {
1301 + $source = new Visualizer_Source_Csv_Remote( $remote_data );
1302 + }
1200 1303 if ( isset( $_POST['vz-import-time'] ) ) {
1201 1304 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1202 1305 }
1203 - // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1204 - } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
1205 - $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1306 + } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] === 0 ) {
1307 + $local_ext = strtolower( pathinfo( isset( $_FILES['local_data']['name'] ) ? $_FILES['local_data']['name'] : '', PATHINFO_EXTENSION ) );
1308 + if ( 'xlsx' === $local_ext ) {
1309 + $source = new Visualizer_Source_Xlsx( $_FILES['local_data']['tmp_name'] );
1310 + } else {
1311 + $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1312 + }
1206 1313 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1207 1314 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1208 1315 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1209 1316 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
@@ -1210,10 +1317,10 @@
1210 1317 $source = $this->handleTabularData();
1211 1318 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1212 1319 } else {
1213 1320 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'CSV file with chart data was not uploaded for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1214 - $render->message = esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' );
1215 - update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' ) );
1321 + $render->message = esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' );
1322 + update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' ) );
1216 1323 }
1217 1324
1218 1325 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Uploaded data for chart %d with source %s', $chart_id, print_r( $source, true ) ), 'debug', __FILE__, __LINE__ );
1219 1326
@@ -1261,9 +1368,9 @@
1261 1368 $render->settings = json_encode( $settings );
1262 1369 } else {
1263 1370 $error = $source->get_error();
1264 1371 if ( empty( $error ) ) {
1265 - $error = esc_html__( 'CSV file is broken or invalid. Please try again.', 'visualizer' );
1372 + $error = esc_html__( 'The CSV file couldn\'t be read. Check that it\'s properly formatted and try again.', 'visualizer' );
1266 1373 }
1267 1374 $render->message = $error;
1268 1375 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1269 1376 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
@@ -1391,10 +1498,11 @@
1391 1498 'visualizer-render',
1392 1499 'visualizer',
1393 1500 array(
1394 1501 'l10n' => array(
1395 - 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
1396 - 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1502 + 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
1503 + 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1504 + 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. To display percentages, use the Manual Configuration option instead.', 'visualizer' ),
1397 1505 ),
1398 1506 'charts' => array(
1399 1507 'canvas' => $data,
1400 1508 ),
@@ -1420,12 +1528,24 @@
1420 1528 */
1421 1529 public function getQueryData() {
1422 1530 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1423 1531
1532 + if ( ! current_user_can( 'administrator' ) ) {
1533 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1534 + }
1535 + if ( ! is_super_admin() ) {
1536 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1537 + }
1538 +
1539 + if ( ! Visualizer_Module::is_pro() ) {
1540 + wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1541 + }
1542 +
1424 1543 $params = wp_parse_args( $_POST['params'] );
1425 1544 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1545 + $query = trim( $params['query'], ';' );
1426 1546
1427 - $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1547 + $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1428 1548 $html = $source->fetch( true );
1429 1549 $error = $source->get_error();
1430 1550 if ( ! empty( $error ) ) {
1431 1551 wp_send_json_error( array( 'msg' => $error ) );
@@ -1440,8 +1560,19 @@
1440 1560 */
1441 1561 public function saveQuery() {
1442 1562 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1443 1563
1564 + if ( ! current_user_can( 'administrator' ) ) {
1565 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1566 + }
1567 + if ( ! is_super_admin() ) {
1568 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1569 + }
1570 +
1571 + if ( ! Visualizer_Module::is_pro() ) {
1572 + wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1573 + }
1574 +
1444 1575 $chart_id = filter_input(
1445 1576 INPUT_GET,
1446 1577 'chart',
1447 1578 FILTER_VALIDATE_INT,
@@ -1470,13 +1601,14 @@
1470 1601
1471 1602 $render = new Visualizer_Render_Page_Update();
1472 1603 if ( $chart_id ) {
1473 1604 $params = wp_parse_args( $_POST['params'] );
1474 - $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1605 + $query = trim( $params['query'], ';' );
1606 + $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1475 1607 $source->fetch( false );
1476 1608 $error = $source->get_error();
1477 1609 if ( empty( $error ) ) {
1478 - update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $params['query'] ) );
1610 + update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $query ) );
1479 1611 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1480 1612 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1481 1613 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1482 1614 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
@@ -1532,9 +1664,9 @@
1532 1664
1533 1665 $hours = filter_input(
1534 1666 INPUT_POST,
1535 1667 'refresh',
1536 - FILTER_VALIDATE_INT,
1668 + FILTER_VALIDATE_FLOAT,
1537 1669 array(
1538 1670 'options' => array(
1539 1671 'min_range' => -1,
1540 1672 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
@@ -1541,9 +1673,9 @@
1541 1673 ),
1542 1674 )
1543 1675 );
1544 1676
1545 - if ( 0 !== $hours && empty( $hours ) ) {
1677 + if ( ! is_numeric( $hours ) ) {
1546 1678 $hours = -1;
1547 1679 }
1548 1680
1549 1681 do_action( 'visualizer_save_filter', $chart_id, $hours );
@@ -1586,8 +1718,12 @@
1586 1718 // Save the image in the uploads directory.
1587 1719 require_once ABSPATH . '/wp-admin/includes/file.php';
1588 1720 \WP_Filesystem();
1589 1721 global $wp_filesystem;
1722 + if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
1723 + $creds = request_filesystem_credentials( site_url() );
1724 + wp_filesystem( $creds );
1725 + }
1590 1726 $upload_file = $wp_filesystem->put_contents( $upload_path . $hashed_filename, $decoded );
1591 1727
1592 1728 // Insert new chart image.
1593 1729 $attachment = array(