PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.5
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.5
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 +215 -75 3.10.114.0.5 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.
@@ -170,8 +169,12 @@
170 169 */
171 170 public function getJsonRoots() {
172 171 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION, 'security' );
173 172
173 + if ( ! current_user_can( 'edit_posts' ) ) {
174 + wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ) );
175 + }
176 +
174 177 $params = wp_parse_args( $_POST['params'] );
175 178
176 179 $source = new Visualizer_Source_Json( $params );
177 180
@@ -192,8 +195,12 @@
192 195 */
193 196 public function getJsonData() {
194 197 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION, 'security' );
195 198
199 + if ( ! current_user_can( 'edit_posts' ) ) {
200 + wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ) );
201 + }
202 +
196 203 $params = wp_parse_args( $_POST['params'] );
197 204
198 205 $chart_id = $params['chart'];
199 206
@@ -296,9 +303,9 @@
296 303 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
297 304 $render->series = json_encode( $source->getSeries() );
298 305 $render->render();
299 306
300 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
307 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
301 308 }
302 309
303 310
304 311 /**
@@ -374,13 +381,13 @@
374 381 * @since 1.0.0
375 382 *
376 383 * @access private
377 384 *
378 - * @param WP_Post $chart The chart object.
385 + * @param WP_Post|null $chart The chart object.
379 386 *
380 387 * @return array The array of chart data.
381 388 */
382 - private function _getChartArray( WP_Post $chart = null ) {
389 + private function _getChartArray( $chart = null ) {
383 390 if ( is_null( $chart ) ) {
384 391 $chart = $this->_chart;
385 392 }
386 393 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
@@ -402,8 +409,13 @@
402 409 }
403 410
404 411 $date_formats = Visualizer_Source::get_date_formats_if_exists( $series, $data );
405 412
413 + $code = '';
414 + if ( 'd3' === $library ) {
415 + $code = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
416 + }
417 +
406 418 return array(
407 419 'type' => $type,
408 420 'series' => $series,
409 421 'settings' => $settings,
@@ -408,8 +420,9 @@
408 420 'series' => $series,
409 421 'settings' => $settings,
410 422 'data' => $data,
411 423 'library' => $library,
424 + 'code' => $code,
412 425 'css' => $css,
413 426 'date_formats' => $date_formats,
414 427 );
415 428 }
@@ -426,9 +439,9 @@
426 439 public static function _sendResponse( $results ) {
427 440 header( 'Content-type: application/json' );
428 441 nocache_headers();
429 442 echo json_encode( $results );
430 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
443 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
431 444 }
432 445
433 446 /**
434 447 * Deletes a chart from database.
@@ -523,8 +536,12 @@
523 536 *
524 537 * @access public
525 538 */
526 539 public function renderChartPages() {
540 + if ( ! current_user_can( 'edit_posts' ) ) {
541 + wp_die( __( 'You do not have permission to access this page.', 'visualizer' ) );
542 + }
543 +
527 544 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
528 545 if ( ! defined( 'ET_BUILDER_PRODUCT_VERSION' ) && function_exists( 'et_get_theme_version' ) ) {
529 546 define( 'ET_BUILDER_PRODUCT_VERSION', et_get_theme_version() );
530 547 }
@@ -534,9 +551,10 @@
534 551 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
535 552 if ( ! empty( $_POST ) ) {
536 553 $_POST = map_deep( $_POST, 'wp_strip_all_tags' );
537 554 }
538 - if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
555 + $chart = $chart_id ? get_post( $chart_id ) : null;
556 + if ( ! $chart_id || ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
539 557 if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
540 558 $this->deleteOldCharts();
541 559 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
542 560 $chart_status = Visualizer_Module_Admin::checkChartStatus( $default_type );
@@ -570,35 +588,33 @@
570 588
571 589 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
572 590 }
573 591 } 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 - );
592 + $parent_chart_id = filter_var( $_GET['parent_chart_id'], FILTER_VALIDATE_INT );
593 + $success = false;
594 + if ( $parent_chart_id ) {
595 + $parent_chart = get_post( $parent_chart_id );
596 + $success = $parent_chart && $parent_chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
597 + }
598 + if ( $success ) {
599 + $new_chart_id = wp_insert_post(
600 + array(
601 + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
602 + 'post_title' => 'Visualization',
603 + 'post_author' => get_current_user_id(),
604 + 'post_status' => $parent_chart->post_status,
605 + 'post_content' => $parent_chart->post_content,
606 + )
607 + );
591 608
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 - }
609 + if ( is_wp_error( $new_chart_id ) ) {
610 + 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__ );
611 + } else {
612 + $post_meta = get_post_meta( $parent_chart_id );
613 + $chart_id = $new_chart_id;
614 + foreach ( $post_meta as $key => $value ) {
615 + if ( strpos( $key, 'visualizer-' ) !== false ) {
616 + add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
601 617 }
602 618 }
603 619 }
604 620 }
@@ -605,9 +621,9 @@
605 621 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
606 622 }
607 623 wp_redirect( esc_url_raw( add_query_arg( 'chart', (int) $chart_id ) ) );
608 624
609 - if ( defined( 'WP_TESTS_DOMAIN' ) ) {
625 + if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
610 626 wp_die();
611 627 }
612 628 exit();
613 629 }
@@ -703,9 +719,9 @@
703 719 default:
704 720 // this should never happen.
705 721 break;
706 722 }
707 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
723 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
708 724 }
709 725
710 726 /**
711 727 * Load code editor assets.
@@ -735,9 +751,9 @@
735 751 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
736 752 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
737 753 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
738 754 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 );
755 + 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 756 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
741 757 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
742 758
743 759 wp_enqueue_script( 'visualizer-codemirror-hint' );
@@ -751,9 +767,9 @@
751 767 'lineWrapping' => true,
752 768 'dragDrop' => false,
753 769 'matchBrackets' => true,
754 770 'autoCloseBrackets' => true,
755 - 'extraKeys' => array( 'Ctrl-Space' => 'autocomplete' ),
771 + 'extraKeys' => array( 'Shift-Space' => 'autocomplete' ),
756 772 'hintOptions' => array( 'tables' => $table_col_mapping ),
757 773 ),
758 774 )
759 775 );
@@ -787,9 +803,12 @@
787 803 update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
788 804 }
789 805
790 806 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
791 - if ( $this->_chart->post_status === 'auto-draft' ) {
807 + $is_canceled = isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] );
808 + $is_newly_created = $this->_chart->post_status === 'auto-draft';
809 +
810 + if ( $is_newly_created && ! $is_canceled ) {
792 811 $this->_chart->post_status = 'publish';
793 812
794 813 // 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 814 // we do not want any difference in data so disable revisions temporarily.
@@ -797,10 +816,15 @@
797 816
798 817 wp_update_post( $this->_chart->to_array() );
799 818 }
800 819 // 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 );
820 + if ( ! $is_canceled ) {
821 + $post_settings = $_POST;
822 + $existing = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
823 + if ( isset( $existing['colors'] ) && is_array( $existing['colors'] ) && ! isset( $post_settings['colors'] ) ) {
824 + $post_settings['colors'] = $existing['colors'];
825 + }
826 + update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $post_settings );
803 827
804 828 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
805 829 // this will help in searching with the chart id.
806 830 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
@@ -847,8 +871,9 @@
847 871 wp_enqueue_style( 'visualizer-frame' );
848 872 wp_enqueue_script( 'visualizer-preview' );
849 873 wp_enqueue_script( 'visualizer-chosen' );
850 874 wp_enqueue_script( 'visualizer-render' );
875 + wp_enqueue_code_editor( array( 'type' => 'application/json' ) );
851 876
852 877 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
853 878 wp_enqueue_script( 'visualizer-editor-simple' );
854 879 wp_localize_script(
@@ -856,12 +881,10 @@
856 881 'visualizer1',
857 882 array(
858 883 'ajax' => array(
859 884 'url' => admin_url( 'admin-ajax.php' ),
860 - 'nonces' => array(
861 - ),
862 - 'actions' => array(
863 - ),
885 + 'nonces' => array(),
886 + 'actions' => array(),
864 887 ),
865 888 )
866 889 );
867 890 }
@@ -872,13 +895,15 @@
872 895 'visualizer-render',
873 896 'visualizer',
874 897 array(
875 898 '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' ),
899 + 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
900 + 'loading' => esc_html__( 'Loading...', 'visualizer' ),
901 + 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
902 + 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
903 + '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' ),
904 + 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
905 + 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. Use the Manual Configuration option instead.', 'visualizer' ),
881 906 ),
882 907 'charts' => array(
883 908 'canvas' => $data,
884 909 'id' => $this->_chart->ID,
@@ -922,11 +947,10 @@
922 947 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
923 948 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
924 949 ? esc_html__( 'Save Chart', 'visualizer' )
925 950 : 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 - }
951 +
952 + $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
929 953 } else {
930 954 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
931 955 }
932 956
@@ -949,9 +973,9 @@
949 973 * @access private
950 974 */
951 975 private function _handleTypesPage() {
952 976 // process post request
953 - if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
977 + if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ), 'visualizer-upload-data' ) ) {
954 978 $type = filter_input( INPUT_POST, 'type' );
955 979 $library = filter_input( INPUT_POST, 'chart-library' );
956 980 if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
957 981 if ( empty( $library ) ) {
@@ -1006,8 +1030,80 @@
1006 1030 * Processes the CSV that is sent in the request as a string.
1007 1031 *
1008 1032 * @since 3.2.0
1009 1033 */
1034 + /**
1035 + * Determines whether a remote URL serves an XLSX file.
1036 + *
1037 + * Used as a fallback when the URL path has no recognisable file extension
1038 + * (e.g. SharePoint, signed S3 URLs, or "download?id=…" endpoints).
1039 + *
1040 + * Uses wp_safe_remote_get() to block requests to private/loopback addresses,
1041 + * and streams the response to a temp file so no body data is held in memory
1042 + * regardless of whether the server honours the Range header.
1043 + *
1044 + * The check relies on the ZIP magic number (PK\x03\x04) that every XLSX
1045 + * file begins with, making it immune to misleading Content-Type headers
1046 + * such as application/octet-stream. Content-Type is used as a last-resort
1047 + * fallback only when the temp file is empty (e.g. a HEAD-only server).
1048 + *
1049 + * @access private
1050 + * @param string $url The remote URL to probe.
1051 + * @return bool TRUE if the file appears to be XLSX, FALSE otherwise.
1052 + */
1053 + private static function _url_is_xlsx( $url ) {
1054 + $tmpfile = wp_tempnam( 'visualizer_xlsx_probe' );
1055 + if ( ! $tmpfile ) {
1056 + return false;
1057 + }
1058 +
1059 + $response = wp_safe_remote_get(
1060 + $url,
1061 + array(
1062 + 'timeout' => 10,
1063 + 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ),
1064 + 'headers' => array( 'Range' => 'bytes=0-3' ),
1065 + 'stream' => true,
1066 + 'filename' => $tmpfile,
1067 + )
1068 + );
1069 +
1070 + if ( is_wp_error( $response ) ) {
1071 + @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1072 + return false;
1073 + }
1074 +
1075 + $magic = '';
1076 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1077 + $fh = @fopen( $tmpfile, 'rb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1078 + if ( $fh ) {
1079 + $magic = fread( $fh, 4 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
1080 + fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1081 + }
1082 + @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1083 +
1084 + if ( strlen( $magic ) >= 4 ) {
1085 + // XLSX (and all ZIP-based Office formats) start with PK\x03\x04.
1086 + return $magic === "PK\x03\x04";
1087 + }
1088 +
1089 + // Last resort: server returned an empty body (e.g. ignored Range and
1090 + // returned only headers). Check Content-Type from the same response.
1091 + // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1092 + return false !== strpos(
1093 + wp_remote_retrieve_header( $response, 'content-type' ),
1094 + 'spreadsheetml'
1095 + );
1096 + }
1097 +
1098 + /**
1099 + * Parses a raw CSV string or editor payload and returns a source object.
1100 + *
1101 + * @access private
1102 + * @param string $data The raw CSV data string.
1103 + * @param string $editor_type The editor type ('text' or 'tabular').
1104 + * @return Visualizer_Source|null The populated source object, or null on failure.
1105 + */
1010 1106 private function handleCSVasString( $data, $editor_type ) {
1011 1107 $source = null;
1012 1108
1013 1109 switch ( $editor_type ) {
@@ -1022,9 +1118,9 @@
1022 1118 continue;
1023 1119 }
1024 1120 $row = explode( ',', $row );
1025 1121 $row = array_map(
1026 - function( $r ) {
1122 + function ( $r ) {
1027 1123 return '' === $r ? ' ' : $r;
1028 1124 },
1029 1125 $row
1030 1126 );
@@ -1073,9 +1169,9 @@
1073 1169 foreach ( $types as $type ) {
1074 1170 if ( empty( $type ) ) {
1075 1171 $exclude[] = $index;
1076 1172 }
1077 - $index++;
1173 + ++$index;
1078 1174 }
1079 1175
1080 1176 // when N headers are being renamed, the number of headers increases by N
1081 1177 // because of the way datatable duplicates header information
@@ -1135,9 +1231,13 @@
1135 1231 // otherwise, assume this is a normal web request.
1136 1232 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1137 1233
1138 1234 // validate nonce
1139 - if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1235 + if (
1236 + ! isset( $_GET['nonce'] ) ||
1237 + ! wp_verify_nonce( $_GET['nonce'], 'visualizer-upload-data' ) ||
1238 + ! current_user_can( 'edit_posts' )
1239 + ) {
1140 1240 if ( ! $can_die ) {
1141 1241 return;
1142 1242 }
1143 1243 status_header( 403 );
@@ -1146,9 +1246,15 @@
1146 1246
1147 1247 // check chart, if chart exists
1148 1248 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1149 1249 $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 ) {
1250 + $chart = $chart_id ? get_post( $chart_id ) : null;
1251 + if (
1252 + ! $chart_id ||
1253 + ! $chart ||
1254 + $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ||
1255 + ! current_user_can( 'edit_post', $chart_id )
1256 + ) {
1151 1257 if ( ! $can_die ) {
1152 1258 return;
1153 1259 }
1154 1260 status_header( 400 );
@@ -1195,15 +1301,24 @@
1195 1301 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1196 1302 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1197 1303 }
1198 1304 if ( false !== $remote_data ) {
1199 - $source = new Visualizer_Source_Csv_Remote( $remote_data );
1305 + $remote_ext = strtolower( pathinfo( parse_url( $remote_data, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
1306 + if ( 'xlsx' === $remote_ext || ( 'csv' !== $remote_ext && self::_url_is_xlsx( $remote_data ) ) ) {
1307 + $source = new Visualizer_Source_Xlsx_Remote( $remote_data );
1308 + } else {
1309 + $source = new Visualizer_Source_Csv_Remote( $remote_data );
1310 + }
1200 1311 if ( isset( $_POST['vz-import-time'] ) ) {
1201 1312 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1202 1313 }
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'] );
1314 + } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] === 0 ) {
1315 + $local_ext = strtolower( pathinfo( isset( $_FILES['local_data']['name'] ) ? $_FILES['local_data']['name'] : '', PATHINFO_EXTENSION ) );
1316 + if ( 'xlsx' === $local_ext ) {
1317 + $source = new Visualizer_Source_Xlsx( $_FILES['local_data']['tmp_name'] );
1318 + } else {
1319 + $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1320 + }
1206 1321 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1207 1322 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1208 1323 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1209 1324 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
@@ -1210,10 +1325,10 @@
1210 1325 $source = $this->handleTabularData();
1211 1326 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1212 1327 } else {
1213 1328 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' ) );
1329 + $render->message = esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' );
1330 + 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 1331 }
1217 1332
1218 1333 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 1334
@@ -1261,9 +1376,9 @@
1261 1376 $render->settings = json_encode( $settings );
1262 1377 } else {
1263 1378 $error = $source->get_error();
1264 1379 if ( empty( $error ) ) {
1265 - $error = esc_html__( 'CSV file is broken or invalid. Please try again.', 'visualizer' );
1380 + $error = esc_html__( 'The CSV file couldn\'t be read. Check that it\'s properly formatted and try again.', 'visualizer' );
1266 1381 }
1267 1382 $render->message = $error;
1268 1383 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1269 1384 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
@@ -1274,9 +1389,9 @@
1274 1389 $render->render();
1275 1390 if ( ! $can_die ) {
1276 1391 return;
1277 1392 }
1278 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1393 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1279 1394 }
1280 1395
1281 1396 /**
1282 1397 * Clones the chart.
@@ -1330,9 +1445,9 @@
1330 1445 );
1331 1446 }
1332 1447 }
1333 1448
1334 - if ( defined( 'WP_TESTS_DOMAIN' ) ) {
1449 + if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
1335 1450 wp_die();
1336 1451 }
1337 1452 wp_redirect( $redirect );
1338 1453 exit;
@@ -1365,9 +1480,9 @@
1365 1480 }
1366 1481 }
1367 1482 }
1368 1483
1369 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1484 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1370 1485 }
1371 1486
1372 1487 /**
1373 1488 * Handles chart data page.
@@ -1391,10 +1506,11 @@
1391 1506 'visualizer-render',
1392 1507 'visualizer',
1393 1508 array(
1394 1509 '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' ),
1510 + 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
1511 + 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1512 + '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 1513 ),
1398 1514 'charts' => array(
1399 1515 'canvas' => $data,
1400 1516 ),
@@ -1420,12 +1536,24 @@
1420 1536 */
1421 1537 public function getQueryData() {
1422 1538 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1423 1539
1540 + if ( ! current_user_can( 'administrator' ) ) {
1541 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1542 + }
1543 + if ( ! is_super_admin() ) {
1544 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1545 + }
1546 +
1547 + if ( ! Visualizer_Module::is_pro() ) {
1548 + wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1549 + }
1550 +
1424 1551 $params = wp_parse_args( $_POST['params'] );
1425 1552 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1553 + $query = trim( $params['query'], ';' );
1426 1554
1427 - $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1555 + $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1428 1556 $html = $source->fetch( true );
1429 1557 $error = $source->get_error();
1430 1558 if ( ! empty( $error ) ) {
1431 1559 wp_send_json_error( array( 'msg' => $error ) );
@@ -1440,8 +1568,19 @@
1440 1568 */
1441 1569 public function saveQuery() {
1442 1570 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1443 1571
1572 + if ( ! current_user_can( 'administrator' ) ) {
1573 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1574 + }
1575 + if ( ! is_super_admin() ) {
1576 + wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1577 + }
1578 +
1579 + if ( ! Visualizer_Module::is_pro() ) {
1580 + wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1581 + }
1582 +
1444 1583 $chart_id = filter_input(
1445 1584 INPUT_GET,
1446 1585 'chart',
1447 1586 FILTER_VALIDATE_INT,
@@ -1470,13 +1609,14 @@
1470 1609
1471 1610 $render = new Visualizer_Render_Page_Update();
1472 1611 if ( $chart_id ) {
1473 1612 $params = wp_parse_args( $_POST['params'] );
1474 - $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1613 + $query = trim( $params['query'], ';' );
1614 + $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1475 1615 $source->fetch( false );
1476 1616 $error = $source->get_error();
1477 1617 if ( empty( $error ) ) {
1478 - update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $params['query'] ) );
1618 + update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $query ) );
1479 1619 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1480 1620 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1481 1621 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1482 1622 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
@@ -1505,9 +1645,9 @@
1505 1645 }
1506 1646 }
1507 1647 $render->render();
1508 1648 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1509 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1649 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1510 1650 }
1511 1651 }
1512 1652
1513 1653
@@ -1532,9 +1672,9 @@
1532 1672
1533 1673 $hours = filter_input(
1534 1674 INPUT_POST,
1535 1675 'refresh',
1536 - FILTER_VALIDATE_INT,
1676 + FILTER_VALIDATE_FLOAT,
1537 1677 array(
1538 1678 'options' => array(
1539 1679 'min_range' => -1,
1540 1680 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
@@ -1541,9 +1681,9 @@
1541 1681 ),
1542 1682 )
1543 1683 );
1544 1684
1545 - if ( 0 !== $hours && empty( $hours ) ) {
1685 + if ( ! is_numeric( $hours ) ) {
1546 1686 $hours = -1;
1547 1687 }
1548 1688
1549 1689 do_action( 'visualizer_save_filter', $chart_id, $hours );
@@ -1548,9 +1688,9 @@
1548 1688
1549 1689 do_action( 'visualizer_save_filter', $chart_id, $hours );
1550 1690
1551 1691 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1552 - defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1692 + ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1553 1693 }
1554 1694 }
1555 1695
1556 1696 /**