PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.11.7
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.11.7
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 3.10.4 All 148 releases
← All changes | classes/Visualizer/Module/Chart.php +119 -284 4.0.73.11.7 View file →
@@ -69,8 +69,9 @@
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 +
73 74 }
74 75
75 76 /**
76 77 * Generates the HTML of the sidebar for the chart.
@@ -107,10 +108,11 @@
107 108 */
108 109 public function setJsonSchedule() {
109 110 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION, 'security' );
110 111
111 - $chart_id = isset( $_POST['chart'] ) ? filter_var(
112 - $_POST['chart'],
112 + $chart_id = filter_input(
113 + INPUT_POST,
114 + 'chart',
113 115 FILTER_VALIDATE_INT,
114 116 array(
115 117 'options' => array(
116 118 'min_range' => 1,
@@ -115,18 +117,14 @@
115 117 'options' => array(
116 118 'min_range' => 1,
117 119 ),
118 120 )
119 - ) : false;
121 + );
120 122
121 123 if ( ! $chart_id ) {
122 124 wp_send_json_error();
123 125 }
124 126
125 - if ( ! self::can_edit_chart( $chart_id ) ) {
126 - wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ), 403 );
127 - }
128 -
129 127 $time = filter_input(
130 128 INPUT_POST,
131 129 'time',
132 130 FILTER_VALIDATE_INT,
@@ -172,12 +170,8 @@
172 170 */
173 171 public function getJsonRoots() {
174 172 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION, 'security' );
175 173
176 - if ( ! current_user_can( 'edit_posts' ) ) {
177 - wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ) );
178 - }
179 -
180 174 $params = wp_parse_args( $_POST['params'] );
181 175
182 176 $source = new Visualizer_Source_Json( $params );
183 177
@@ -198,18 +192,13 @@
198 192 */
199 193 public function getJsonData() {
200 194 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION, 'security' );
201 195
202 - if ( ! current_user_can( 'edit_posts' ) ) {
203 - wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ) );
204 - }
205 -
206 196 $params = wp_parse_args( $_POST['params'] );
207 197
208 198 $chart_id = $params['chart'];
209 199
210 - $chart = $chart_id ? get_post( $chart_id ) : null;
211 - if ( ! $chart || Visualizer_Plugin::CPT_VISUALIZER !== $chart->post_type || ! current_user_can( 'edit_post', $chart_id ) ) {
200 + if ( empty( $chart_id ) ) {
212 201 wp_die();
213 202 }
214 203
215 204 $source = new Visualizer_Source_Json( $params );
@@ -234,12 +223,12 @@
234 223 public function setJsonData() {
235 224 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_DATA . Visualizer_Plugin::VERSION, 'security' );
236 225
237 226 $params = $_POST;
238 - $chart_id = isset( $_GET['chart'] ) ? absint( $_GET['chart'] ) : 0;
227 + $chart_id = $_GET['chart'];
239 228
240 - if ( ! self::can_edit_chart( $chart_id ) ) {
241 - wp_die( esc_html__( 'You do not have permission to perform this action.', 'visualizer' ), '', array( 'response' => 403 ) );
229 + if ( empty( $chart_id ) ) {
230 + wp_die();
242 231 }
243 232
244 233 $chart = get_post( $chart_id );
245 234
@@ -307,9 +296,9 @@
307 296 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
308 297 $render->series = json_encode( $source->getSeries() );
309 298 $render->render();
310 299
311 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
300 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
312 301 }
313 302
314 303
315 304 /**
@@ -321,14 +310,8 @@
321 310 *
322 311 * @access public
323 312 */
324 313 public function getCharts() {
325 - check_ajax_referer( Visualizer_Plugin::ACTION_GET_CHARTS, 'nonce' );
326 -
327 - if ( ! current_user_can( 'edit_posts' ) ) {
328 - wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ), 403 );
329 - }
330 -
331 314 $query_args = array(
332 315 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
333 316 'posts_per_page' => 9,
334 317 'paged' => filter_input(
@@ -342,11 +325,8 @@
342 325 ),
343 326 )
344 327 ),
345 328 );
346 - if ( ! current_user_can( 'edit_others_posts' ) ) {
347 - $query_args['author'] = get_current_user_id();
348 - }
349 329 $filter = filter_input( INPUT_GET, 's', FILTER_SANITIZE_STRING );
350 330 if ( empty( $filter ) ) {
351 331 // 'filter' is from the modal from the add media button.
352 332 $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
@@ -394,13 +374,13 @@
394 374 * @since 1.0.0
395 375 *
396 376 * @access private
397 377 *
398 - * @param WP_Post|null $chart The chart object.
378 + * @param WP_Post $chart The chart object.
399 379 *
400 380 * @return array The array of chart data.
401 381 */
402 - private function _getChartArray( $chart = null ) {
382 + private function _getChartArray( WP_Post $chart = null ) {
403 383 if ( is_null( $chart ) ) {
404 384 $chart = $this->_chart;
405 385 }
406 386 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
@@ -422,13 +402,8 @@
422 402 }
423 403
424 404 $date_formats = Visualizer_Source::get_date_formats_if_exists( $series, $data );
425 405
426 - $code = '';
427 - if ( 'd3' === $library ) {
428 - $code = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
429 - }
430 -
431 406 return array(
432 407 'type' => $type,
433 408 'series' => $series,
434 409 'settings' => $settings,
@@ -433,9 +408,8 @@
433 408 'series' => $series,
434 409 'settings' => $settings,
435 410 'data' => $data,
436 411 'library' => $library,
437 - 'code' => $code,
438 412 'css' => $css,
439 413 'date_formats' => $date_formats,
440 414 );
441 415 }
@@ -452,9 +426,9 @@
452 426 public static function _sendResponse( $results ) {
453 427 header( 'Content-type: application/json' );
454 428 nocache_headers();
455 429 echo json_encode( $results );
456 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
430 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
457 431 }
458 432
459 433 /**
460 434 * Deletes a chart from database.
@@ -465,14 +439,16 @@
465 439 * @access public
466 440 */
467 441 public function deleteChart() {
468 442 $is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
469 - $input = $is_post ? $_POST : $_GET;
443 + $input_method = $is_post ? INPUT_POST : INPUT_GET;
470 444 $chart_id = $success = false;
471 - $nonce = isset( $input['nonce'] ) && wp_verify_nonce( $input['nonce'] );
472 - if ( $nonce ) {
473 - $chart_id = isset( $input['chart'] ) ? filter_var(
474 - $input['chart'],
445 + $nonce = wp_verify_nonce( filter_input( $input_method, 'nonce' ) );
446 + $capable = current_user_can( 'delete_posts' );
447 + if ( $nonce && $capable ) {
448 + $chart_id = filter_input(
449 + $input_method,
450 + 'chart',
475 451 FILTER_VALIDATE_INT,
476 452 array(
477 453 'options' => array(
478 454 'min_range' => 1,
@@ -477,17 +453,12 @@
477 453 'options' => array(
478 454 'min_range' => 1,
479 455 ),
480 456 )
481 - ) : false;
457 + );
482 458 if ( $chart_id ) {
483 459 $chart = get_post( $chart_id );
484 - $success = $chart
485 - && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER
486 - && (
487 - current_user_can( 'delete_post', $chart_id )
488 - || ( (int) $chart->post_author === get_current_user_id() && current_user_can( 'delete_posts' ) )
489 - );
460 + $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
490 461 }
491 462 }
492 463 if ( $success ) {
493 464 global $sitepress;
@@ -552,12 +523,8 @@
552 523 *
553 524 * @access public
554 525 */
555 526 public function renderChartPages() {
556 - if ( ! current_user_can( 'edit_posts' ) ) {
557 - wp_die( __( 'You do not have permission to access this page.', 'visualizer' ) );
558 - }
559 -
560 527 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
561 528 if ( ! defined( 'ET_BUILDER_PRODUCT_VERSION' ) && function_exists( 'et_get_theme_version' ) ) {
562 529 define( 'ET_BUILDER_PRODUCT_VERSION', et_get_theme_version() );
563 530 }
@@ -567,13 +534,9 @@
567 534 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
568 535 if ( ! empty( $_POST ) ) {
569 536 $_POST = map_deep( $_POST, 'wp_strip_all_tags' );
570 537 }
571 - $chart = $chart_id ? get_post( $chart_id ) : null;
572 - if ( $chart && ! self::can_edit_chart( $chart_id ) ) {
573 - wp_die( esc_html__( 'You do not have permission to access this page.', 'visualizer' ), '', array( 'response' => 403 ) );
574 - }
575 - if ( ! $chart_id || ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
538 + if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
576 539 if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
577 540 $this->deleteOldCharts();
578 541 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
579 542 $chart_status = Visualizer_Module_Admin::checkChartStatus( $default_type );
@@ -607,33 +570,35 @@
607 570
608 571 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
609 572 }
610 573 } else {
611 - $parent_chart_id = filter_var( $_GET['parent_chart_id'], FILTER_VALIDATE_INT );
612 - $success = false;
613 - if ( $parent_chart_id && self::can_edit_chart( $parent_chart_id ) ) {
614 - $parent_chart = get_post( $parent_chart_id );
615 - $success = $parent_chart && $parent_chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
616 - }
617 - if ( $success ) {
618 - $new_chart_id = wp_insert_post(
619 - array(
620 - 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
621 - 'post_title' => 'Visualization',
622 - 'post_author' => get_current_user_id(),
623 - 'post_status' => $parent_chart->post_status,
624 - 'post_content' => $parent_chart->post_content,
625 - )
626 - );
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 + );
627 591
628 - if ( is_wp_error( $new_chart_id ) ) {
629 - 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__ );
630 - } else {
631 - $post_meta = get_post_meta( $parent_chart_id );
632 - $chart_id = $new_chart_id;
633 - foreach ( $post_meta as $key => $value ) {
634 - if ( strpos( $key, 'visualizer-' ) !== false ) {
635 - add_post_meta( $new_chart_id, $key, self::maybe_decode_content( $value[0] ) );
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 + }
636 601 }
637 602 }
638 603 }
639 604 }
@@ -640,9 +605,9 @@
640 605 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
641 606 }
642 607 wp_redirect( esc_url_raw( add_query_arg( 'chart', (int) $chart_id ) ) );
643 608
644 - if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
609 + if ( defined( 'WP_TESTS_DOMAIN' ) ) {
645 610 wp_die();
646 611 }
647 612 exit();
648 613 }
@@ -738,9 +703,9 @@
738 703 default:
739 704 // this should never happen.
740 705 break;
741 706 }
742 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
707 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
743 708 }
744 709
745 710 /**
746 711 * Load code editor assets.
@@ -770,9 +735,9 @@
770 735 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
771 736 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
772 737 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
773 738 wp_register_script( 'visualizer-codemirror-sql-hint', '//codemirror.net/addon/hint/sql-hint.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
774 - 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 );
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 );
775 740 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
776 741 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
777 742
778 743 wp_enqueue_script( 'visualizer-codemirror-hint' );
@@ -817,16 +782,16 @@
817 782 /**
818 783 * Handle data and settings page
819 784 */
820 785 private function _handleDataAndSettingsPage() {
786 + if ( isset( $_POST['map_api_key'] ) ) {
787 + update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
788 + }
789 +
821 790 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
822 791 $is_canceled = isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] );
823 792 $is_newly_created = $this->_chart->post_status === 'auto-draft';
824 793
825 - if ( isset( $_POST['map_api_key'] ) && current_user_can( 'manage_options' ) ) {
826 - update_option( 'visualizer-map-api-key', sanitize_text_field( wp_unslash( $_POST['map_api_key'] ) ) );
827 - }
828 -
829 794 if ( $is_newly_created && ! $is_canceled ) {
830 795 $this->_chart->post_status = 'publish';
831 796
832 797 // 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.
@@ -836,15 +801,9 @@
836 801 wp_update_post( $this->_chart->to_array() );
837 802 }
838 803 // save meta data only when it is NOT being canceled.
839 804 if ( ! $is_canceled ) {
840 - $post_settings = $_POST;
841 - $existing = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
842 - if ( isset( $existing['colors'] ) && is_array( $existing['colors'] ) && ! isset( $post_settings['colors'] ) ) {
843 - $post_settings['colors'] = $existing['colors'];
844 - }
845 - $post_settings = $this->sanitizeSettings( $post_settings );
846 - update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $post_settings );
805 + update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $_POST );
847 806
848 807 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
849 808 // this will help in searching with the chart id.
850 809 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
@@ -891,9 +850,8 @@
891 850 wp_enqueue_style( 'visualizer-frame' );
892 851 wp_enqueue_script( 'visualizer-preview' );
893 852 wp_enqueue_script( 'visualizer-chosen' );
894 853 wp_enqueue_script( 'visualizer-render' );
895 - wp_enqueue_code_editor( array( 'type' => 'application/json' ) );
896 854
897 855 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
898 856 wp_enqueue_script( 'visualizer-editor-simple' );
899 857 wp_localize_script(
@@ -901,10 +859,12 @@
901 859 'visualizer1',
902 860 array(
903 861 'ajax' => array(
904 862 'url' => admin_url( 'admin-ajax.php' ),
905 - 'nonces' => array(),
906 - 'actions' => array(),
863 + 'nonces' => array(
864 + ),
865 + 'actions' => array(
866 + ),
907 867 ),
908 868 )
909 869 );
910 870 }
@@ -915,15 +875,14 @@
915 875 'visualizer-render',
916 876 'visualizer',
917 877 array(
918 878 'l10n' => array(
919 - 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
920 - 'loading' => esc_html__( 'Loading...', 'visualizer' ),
921 - 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
922 - 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
923 - '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' ),
924 - 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
925 - 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. Use the Manual Configuration option instead.', 'visualizer' ),
879 + 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
880 + 'loading' => esc_html__( 'Loading...', 'visualizer' ),
881 + 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
882 + 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
883 + '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' ),
884 + 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
926 885 ),
927 886 'charts' => array(
928 887 'canvas' => $data,
929 888 'id' => $this->_chart->ID,
@@ -993,9 +952,9 @@
993 952 * @access private
994 953 */
995 954 private function _handleTypesPage() {
996 955 // process post request
997 - if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ), 'visualizer-upload-data' ) ) {
956 + if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
998 957 $type = filter_input( INPUT_POST, 'type' );
999 958 $library = filter_input( INPUT_POST, 'chart-library' );
1000 959 if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
1001 960 if ( empty( $library ) ) {
@@ -1034,35 +993,8 @@
1034 993 wp_iframe( array( $render, 'render' ) );
1035 994 }
1036 995
1037 996 /**
1038 - * Sanitize settings data from the request.
1039 - *
1040 - * @param array<string, mixed> $post_data The POST data to sanitize.
1041 - * @return array<string, mixed> The sanitized settings data.
1042 - */
1043 - private function sanitizeSettings( $post_data ): array {
1044 - $chart_img = '';
1045 - if ( isset( $post_data['chart-img'] ) ) {
1046 - $chart_img = wp_unslash( $post_data['chart-img'] );
1047 - unset( $post_data['chart-img'] );
1048 - }
1049 -
1050 - $post_data = map_deep(
1051 - $post_data,
1052 - 'sanitize_textarea_field'
1053 - );
1054 -
1055 - // The value is a client-side canvas export; keep it only when it is a
1056 - // base64 image data URI so nothing else is ever stored unsanitized.
1057 - if ( is_string( $chart_img ) && preg_match( '#^data:image/(png|jpeg|webp);base64,[A-Za-z0-9+/ ]+=*$#', $chart_img ) ) {
1058 - $post_data['chart-img'] = $chart_img;
1059 - }
1060 -
1061 - return $post_data;
1062 - }
1063 -
1064 - /**
1065 997 * Renders flattr script in the iframe <head>
1066 998 *
1067 999 * @since 1.4.2
1068 1000 * @action admin_head
@@ -1077,81 +1009,8 @@
1077 1009 * Processes the CSV that is sent in the request as a string.
1078 1010 *
1079 1011 * @since 3.2.0
1080 1012 */
1081 - /**
1082 - * Determines whether a remote URL serves an XLSX file.
1083 - *
1084 - * Used as a fallback when the URL path has no recognisable file extension
1085 - * (e.g. SharePoint, signed S3 URLs, or "download?id=…" endpoints).
1086 - *
1087 - * Uses the shared remote-fetch policy to block non-public destinations,
1088 - * and streams the response to a temp file so no body data is held in memory
1089 - * regardless of whether the server honours the Range header.
1090 - *
1091 - * The check relies on the ZIP magic number (PK\x03\x04) that every XLSX
1092 - * file begins with, making it immune to misleading Content-Type headers
1093 - * such as application/octet-stream. Content-Type is used as a last-resort
1094 - * fallback only when the temp file is empty (e.g. a HEAD-only server).
1095 - *
1096 - * @access private
1097 - * @param string $url The remote URL to probe.
1098 - * @return bool TRUE if the file appears to be XLSX, FALSE otherwise.
1099 - */
1100 - private static function _url_is_xlsx( $url ) {
1101 - $tmpfile = wp_tempnam( 'visualizer_xlsx_probe' );
1102 - if ( ! $tmpfile ) {
1103 - return false;
1104 - }
1105 -
1106 - $response = Visualizer_Remote_Fetch::request(
1107 - $url,
1108 - array(
1109 - 'timeout' => 10,
1110 - 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ),
1111 - 'headers' => array( 'Range' => 'bytes=0-3' ),
1112 - 'stream' => true,
1113 - 'filename' => $tmpfile,
1114 - 'limit_response_size' => 4,
1115 - )
1116 - );
1117 -
1118 - if ( is_wp_error( $response ) ) {
1119 - @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1120 - return false;
1121 - }
1122 -
1123 - $magic = '';
1124 - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1125 - $fh = @fopen( $tmpfile, 'rb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1126 - if ( $fh ) {
1127 - $magic = fread( $fh, 4 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
1128 - fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1129 - }
1130 - @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1131 -
1132 - if ( strlen( $magic ) >= 4 ) {
1133 - // XLSX (and all ZIP-based Office formats) start with PK\x03\x04.
1134 - return $magic === "PK\x03\x04";
1135 - }
1136 -
1137 - // Last resort: server returned an empty body (e.g. ignored Range and
1138 - // returned only headers). Check Content-Type from the same response.
1139 - // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1140 - return false !== strpos(
1141 - wp_remote_retrieve_header( $response, 'content-type' ),
1142 - 'spreadsheetml'
1143 - );
1144 - }
1145 -
1146 - /**
1147 - * Parses a raw CSV string or editor payload and returns a source object.
1148 - *
1149 - * @access private
1150 - * @param string $data The raw CSV data string.
1151 - * @param string $editor_type The editor type ('text' or 'tabular').
1152 - * @return Visualizer_Source|null The populated source object, or null on failure.
1153 - */
1154 1013 private function handleCSVasString( $data, $editor_type ) {
1155 1014 $source = null;
1156 1015
1157 1016 switch ( $editor_type ) {
@@ -1166,9 +1025,9 @@
1166 1025 continue;
1167 1026 }
1168 1027 $row = explode( ',', $row );
1169 1028 $row = array_map(
1170 - function ( $r ) {
1029 + function( $r ) {
1171 1030 return '' === $r ? ' ' : $r;
1172 1031 },
1173 1032 $row
1174 1033 );
@@ -1217,9 +1076,9 @@
1217 1076 foreach ( $types as $type ) {
1218 1077 if ( empty( $type ) ) {
1219 1078 $exclude[] = $index;
1220 1079 }
1221 - ++$index;
1080 + $index++;
1222 1081 }
1223 1082
1224 1083 // when N headers are being renamed, the number of headers increases by N
1225 1084 // because of the way datatable duplicates header information
@@ -1277,16 +1136,11 @@
1277 1136 public function uploadData() {
1278 1137 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1279 1138 // otherwise, assume this is a normal web request.
1280 1139 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1281 - // $can_die also gates the capability checks below, so VISUALIZER_DO_NOT_DIE must stay internal-only (never set from request input or globally).
1282 1140
1283 - // validate nonce; capability check applies to web requests only, not trusted internal calls.
1284 - if (
1285 - ! isset( $_GET['nonce'] ) ||
1286 - ! wp_verify_nonce( $_GET['nonce'], 'visualizer-upload-data' ) ||
1287 - ( $can_die && ! current_user_can( 'edit_posts' ) )
1288 - ) {
1141 + // validate nonce
1142 + if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1289 1143 if ( ! $can_die ) {
1290 1144 return;
1291 1145 }
1292 1146 status_header( 403 );
@@ -1295,15 +1149,9 @@
1295 1149
1296 1150 // check chart, if chart exists
1297 1151 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1298 1152 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1299 - $chart = $chart_id ? get_post( $chart_id ) : null;
1300 - if (
1301 - ! $chart_id ||
1302 - ! $chart ||
1303 - $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ||
1304 - ( $can_die && ! current_user_can( 'edit_post', $chart_id ) )
1305 - ) {
1153 + if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
1306 1154 if ( ! $can_die ) {
1307 1155 return;
1308 1156 }
1309 1157 status_header( 400 );
@@ -1350,24 +1198,15 @@
1350 1198 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1351 1199 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1352 1200 }
1353 1201 if ( false !== $remote_data ) {
1354 - $remote_ext = strtolower( pathinfo( parse_url( $remote_data, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
1355 - if ( 'xlsx' === $remote_ext || ( 'csv' !== $remote_ext && self::_url_is_xlsx( $remote_data ) ) ) {
1356 - $source = new Visualizer_Source_Xlsx_Remote( $remote_data );
1357 - } else {
1358 - $source = new Visualizer_Source_Csv_Remote( $remote_data );
1359 - }
1202 + $source = new Visualizer_Source_Csv_Remote( $remote_data );
1360 1203 if ( isset( $_POST['vz-import-time'] ) ) {
1361 1204 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1362 1205 }
1363 - } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] === 0 ) {
1364 - $local_ext = strtolower( pathinfo( isset( $_FILES['local_data']['name'] ) ? $_FILES['local_data']['name'] : '', PATHINFO_EXTENSION ) );
1365 - if ( 'xlsx' === $local_ext ) {
1366 - $source = new Visualizer_Source_Xlsx( $_FILES['local_data']['tmp_name'] );
1367 - } else {
1368 - $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1369 - }
1206 + // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1207 + } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
1208 + $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1370 1209 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1371 1210 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1372 1211 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1373 1212 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
@@ -1374,10 +1213,10 @@
1374 1213 $source = $this->handleTabularData();
1375 1214 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1376 1215 } else {
1377 1216 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__ );
1378 - $render->message = esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' );
1379 - update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' ) );
1217 + $render->message = esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' );
1218 + update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' ) );
1380 1219 }
1381 1220
1382 1221 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__ );
1383 1222
@@ -1384,10 +1223,10 @@
1384 1223 if ( $source ) {
1385 1224 if ( $source->fetch() ) {
1386 1225 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1387 1226 $populate = true;
1388 - $json = self::decode_content( $content );
1389 - if ( is_array( $json ) ) {
1227 + if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {
1228 + $json = unserialize( $content );
1390 1229 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1391 1230 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1392 1231 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
1393 1232 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Not populating chart data as source exists (%s) but data is empty!', $json['source'] ), 'warn', __FILE__, __LINE__ );
@@ -1425,9 +1264,9 @@
1425 1264 $render->settings = json_encode( $settings );
1426 1265 } else {
1427 1266 $error = $source->get_error();
1428 1267 if ( empty( $error ) ) {
1429 - $error = esc_html__( 'The CSV file couldn\'t be read. Check that it\'s properly formatted and try again.', 'visualizer' );
1268 + $error = esc_html__( 'CSV file is broken or invalid. Please try again.', 'visualizer' );
1430 1269 }
1431 1270 $render->message = $error;
1432 1271 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1433 1272 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
@@ -1438,9 +1277,9 @@
1438 1277 $render->render();
1439 1278 if ( ! $can_die ) {
1440 1279 return;
1441 1280 }
1442 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1281 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1443 1282 }
1444 1283
1445 1284 /**
1446 1285 * Clones the chart.
@@ -1451,11 +1290,12 @@
1451 1290 */
1452 1291 public function cloneChart() {
1453 1292 $chart_id = $success = false;
1454 1293 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1455 - if ( $nonce ) {
1294 + $capable = current_user_can( 'edit_posts' );
1295 + if ( $nonce && $capable ) {
1456 1296 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1457 - if ( $chart_id && self::can_edit_chart( $chart_id ) ) {
1297 + if ( $chart_id ) {
1458 1298 $chart = get_post( $chart_id );
1459 1299 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1460 1300 }
1461 1301 }
@@ -1475,9 +1315,9 @@
1475 1315 } else {
1476 1316 $post_meta = get_post_meta( $chart_id );
1477 1317 foreach ( $post_meta as $key => $value ) {
1478 1318 if ( strpos( $key, 'visualizer-' ) !== false ) {
1479 - add_post_meta( $new_chart_id, $key, self::maybe_decode_content( $value[0] ) );
1319 + add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
1480 1320 }
1481 1321 }
1482 1322 $redirect = esc_url(
1483 1323 add_query_arg(
@@ -1493,9 +1333,9 @@
1493 1333 );
1494 1334 }
1495 1335 }
1496 1336
1497 - if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
1337 + if ( defined( 'WP_TESTS_DOMAIN' ) ) {
1498 1338 wp_die();
1499 1339 }
1500 1340 wp_redirect( $redirect );
1501 1341 exit;
@@ -1509,25 +1349,28 @@
1509 1349 * @access public
1510 1350 */
1511 1351 public function exportData() {
1512 1352 check_ajax_referer( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION, 'security' );
1513 - $chart_id = isset( $_GET['chart'] ) ? filter_var(
1514 - $_GET['chart'],
1515 - FILTER_VALIDATE_INT,
1516 - array(
1517 - 'options' => array(
1518 - 'min_range' => 1,
1519 - ),
1520 - )
1521 - ) : '';
1522 - if ( $chart_id && self::can_edit_chart( $chart_id ) ) {
1523 - $data = $this->_getDataAs( $chart_id, 'csv' );
1524 - if ( $data ) {
1525 - echo wp_send_json_success( $data );
1353 + $capable = current_user_can( 'edit_posts' );
1354 + if ( $capable ) {
1355 + $chart_id = isset( $_GET['chart'] ) ? filter_var(
1356 + $_GET['chart'],
1357 + FILTER_VALIDATE_INT,
1358 + array(
1359 + 'options' => array(
1360 + 'min_range' => 1,
1361 + ),
1362 + )
1363 + ) : '';
1364 + if ( $chart_id ) {
1365 + $data = $this->_getDataAs( $chart_id, 'csv' );
1366 + if ( $data ) {
1367 + echo wp_send_json_success( $data );
1368 + }
1526 1369 }
1527 1370 }
1528 1371
1529 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1372 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1530 1373 }
1531 1374
1532 1375 /**
1533 1376 * Handles chart data page.
@@ -1551,11 +1394,10 @@
1551 1394 'visualizer-render',
1552 1395 'visualizer',
1553 1396 array(
1554 1397 'l10n' => array(
1555 - 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
1556 - 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1557 - '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' ),
1398 + 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
1399 + 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1558 1400 ),
1559 1401 'charts' => array(
1560 1402 'canvas' => $data,
1561 1403 ),
@@ -1690,9 +1532,9 @@
1690 1532 }
1691 1533 }
1692 1534 $render->render();
1693 1535 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1694 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1536 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1695 1537 }
1696 1538 }
1697 1539
1698 1540
@@ -1703,10 +1545,11 @@
1703 1545 */
1704 1546 public function saveFilter() {
1705 1547 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1706 1548
1707 - $chart_id = isset( $_GET['chart'] ) ? filter_var(
1708 - $_GET['chart'],
1549 + $chart_id = filter_input(
1550 + INPUT_GET,
1551 + 'chart',
1709 1552 FILTER_VALIDATE_INT,
1710 1553 array(
1711 1554 'options' => array(
1712 1555 'min_range' => 1,
@@ -1711,18 +1554,14 @@
1711 1554 'options' => array(
1712 1555 'min_range' => 1,
1713 1556 ),
1714 1557 )
1715 - ) : false;
1558 + );
1716 1559
1717 - if ( ! self::can_edit_chart( $chart_id ) ) {
1718 - wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ), 403 );
1719 - }
1720 -
1721 1560 $hours = filter_input(
1722 1561 INPUT_POST,
1723 1562 'refresh',
1724 - FILTER_VALIDATE_FLOAT,
1563 + FILTER_VALIDATE_INT,
1725 1564 array(
1726 1565 'options' => array(
1727 1566 'min_range' => -1,
1728 1567 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
@@ -1729,9 +1568,9 @@
1729 1568 ),
1730 1569 )
1731 1570 );
1732 1571
1733 - if ( ! is_numeric( $hours ) ) {
1572 + if ( 0 !== $hours && empty( $hours ) ) {
1734 1573 $hours = -1;
1735 1574 }
1736 1575
1737 1576 do_action( 'visualizer_save_filter', $chart_id, $hours );
@@ -1736,9 +1575,9 @@
1736 1575
1737 1576 do_action( 'visualizer_save_filter', $chart_id, $hours );
1738 1577
1739 1578 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1740 - ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1579 + defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1741 1580 }
1742 1581 }
1743 1582
1744 1583 /**
@@ -1746,9 +1585,9 @@
1746 1585 *
1747 1586 * @param string $base64_img Chart image.
1748 1587 * @param int $chart_id Chart ID.
1749 1588 * @param bool $save_attachment Save attachment.
1750 - * @return int Attachment ID, or 0 when no attachment was saved.
1589 + * @return attachment ID
1751 1590 */
1752 1591 public function save_chart_image( $base64_img, $chart_id, $save_attachment = true ) {
1753 1592 // Delete old chart image.
1754 1593 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
@@ -1763,15 +1602,11 @@
1763 1602 // Upload dir.
1764 1603 $upload_dir = wp_upload_dir();
1765 1604 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1766 1605
1767 - $img = str_replace( 'data:image/png;base64,', '', (string) $base64_img );
1768 - $img = str_replace( ' ', '+', $img );
1769 - $decoded = base64_decode( $img, true );
1770 - // The value comes from an untrusted request; only write real PNG bytes to uploads.
1771 - if ( false === $decoded || 0 !== strncmp( $decoded, "\x89PNG\r\n\x1a\n", 8 ) ) {
1772 - return 0;
1773 - }
1606 + $img = str_replace( 'data:image/png;base64,', '', $base64_img );
1607 + $img = str_replace( ' ', '+', $img );
1608 + $decoded = base64_decode( $img );
1774 1609 $filename = 'visualization-' . $chart_id . '.png';
1775 1610 $file_type = 'image/png';
1776 1611 $hashed_filename = $filename;
1777 1612