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 +63 -115 4.0.84.0.5 View file →
@@ -107,10 +107,11 @@
107 107 */
108 108 public function setJsonSchedule() {
109 109 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION, 'security' );
110 110
111 - $chart_id = isset( $_POST['chart'] ) ? filter_var(
112 - $_POST['chart'],
111 + $chart_id = filter_input(
112 + INPUT_POST,
113 + 'chart',
113 114 FILTER_VALIDATE_INT,
114 115 array(
115 116 'options' => array(
116 117 'min_range' => 1,
@@ -115,18 +116,14 @@
115 116 'options' => array(
116 117 'min_range' => 1,
117 118 ),
118 119 )
119 - ) : false;
120 + );
120 121
121 122 if ( ! $chart_id ) {
122 123 wp_send_json_error();
123 124 }
124 125
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 126 $time = filter_input(
130 127 INPUT_POST,
131 128 'time',
132 129 FILTER_VALIDATE_INT,
@@ -206,10 +203,9 @@
206 203 $params = wp_parse_args( $_POST['params'] );
207 204
208 205 $chart_id = $params['chart'];
209 206
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 ) ) {
207 + if ( empty( $chart_id ) ) {
212 208 wp_die();
213 209 }
214 210
215 211 $source = new Visualizer_Source_Json( $params );
@@ -234,12 +230,12 @@
234 230 public function setJsonData() {
235 231 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_DATA . Visualizer_Plugin::VERSION, 'security' );
236 232
237 233 $params = $_POST;
238 - $chart_id = isset( $_GET['chart'] ) ? absint( $_GET['chart'] ) : 0;
234 + $chart_id = $_GET['chart'];
239 235
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 ) );
236 + if ( empty( $chart_id ) ) {
237 + wp_die();
242 238 }
243 239
244 240 $chart = get_post( $chart_id );
245 241
@@ -321,14 +317,8 @@
321 317 *
322 318 * @access public
323 319 */
324 320 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 321 $query_args = array(
332 322 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
333 323 'posts_per_page' => 9,
334 324 'paged' => filter_input(
@@ -342,11 +332,8 @@
342 332 ),
343 333 )
344 334 ),
345 335 );
346 - if ( ! current_user_can( 'edit_others_posts' ) ) {
347 - $query_args['author'] = get_current_user_id();
348 - }
349 336 $filter = filter_input( INPUT_GET, 's', FILTER_SANITIZE_STRING );
350 337 if ( empty( $filter ) ) {
351 338 // 'filter' is from the modal from the add media button.
352 339 $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
@@ -465,14 +452,16 @@
465 452 * @access public
466 453 */
467 454 public function deleteChart() {
468 455 $is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
469 - $input = $is_post ? $_POST : $_GET;
456 + $input_method = $is_post ? INPUT_POST : INPUT_GET;
470 457 $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'],
458 + $nonce = wp_verify_nonce( filter_input( $input_method, 'nonce' ) );
459 + $capable = current_user_can( 'delete_posts' );
460 + if ( $nonce && $capable ) {
461 + $chart_id = filter_input(
462 + $input_method,
463 + 'chart',
475 464 FILTER_VALIDATE_INT,
476 465 array(
477 466 'options' => array(
478 467 'min_range' => 1,
@@ -477,17 +466,12 @@
477 466 'options' => array(
478 467 'min_range' => 1,
479 468 ),
480 469 )
481 - ) : false;
470 + );
482 471 if ( $chart_id ) {
483 472 $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 - );
473 + $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
490 474 }
491 475 }
492 476 if ( $success ) {
493 477 global $sitepress;
@@ -568,11 +552,8 @@
568 552 if ( ! empty( $_POST ) ) {
569 553 $_POST = map_deep( $_POST, 'wp_strip_all_tags' );
570 554 }
571 555 $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 556 if ( ! $chart_id || ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
576 557 if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
577 558 $this->deleteOldCharts();
578 559 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
@@ -609,9 +590,9 @@
609 590 }
610 591 } else {
611 592 $parent_chart_id = filter_var( $_GET['parent_chart_id'], FILTER_VALIDATE_INT );
612 593 $success = false;
613 - if ( $parent_chart_id && self::can_edit_chart( $parent_chart_id ) ) {
594 + if ( $parent_chart_id ) {
614 595 $parent_chart = get_post( $parent_chart_id );
615 596 $success = $parent_chart && $parent_chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
616 597 }
617 598 if ( $success ) {
@@ -631,9 +612,9 @@
631 612 $post_meta = get_post_meta( $parent_chart_id );
632 613 $chart_id = $new_chart_id;
633 614 foreach ( $post_meta as $key => $value ) {
634 615 if ( strpos( $key, 'visualizer-' ) !== false ) {
635 - add_post_meta( $new_chart_id, $key, self::maybe_decode_content( $value[0] ) );
616 + add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
636 617 }
637 618 }
638 619 }
639 620 }
@@ -817,16 +798,16 @@
817 798 /**
818 799 * Handle data and settings page
819 800 */
820 801 private function _handleDataAndSettingsPage() {
802 + if ( isset( $_POST['map_api_key'] ) ) {
803 + update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
804 + }
805 +
821 806 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
822 807 $is_canceled = isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] );
823 808 $is_newly_created = $this->_chart->post_status === 'auto-draft';
824 809
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 810 if ( $is_newly_created && ! $is_canceled ) {
830 811 $this->_chart->post_status = 'publish';
831 812
832 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.
@@ -841,9 +822,8 @@
841 822 $existing = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
842 823 if ( isset( $existing['colors'] ) && is_array( $existing['colors'] ) && ! isset( $post_settings['colors'] ) ) {
843 824 $post_settings['colors'] = $existing['colors'];
844 825 }
845 - $post_settings = $this->sanitizeSettings( $post_settings );
846 826 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $post_settings );
847 827
848 828 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
849 829 // this will help in searching with the chart id.
@@ -1034,35 +1014,8 @@
1034 1014 wp_iframe( array( $render, 'render' ) );
1035 1015 }
1036 1016
1037 1017 /**
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 1018 * Renders flattr script in the iframe <head>
1066 1019 *
1067 1020 * @since 1.4.2
1068 1021 * @action admin_head
@@ -1083,9 +1036,9 @@
1083 1036 *
1084 1037 * Used as a fallback when the URL path has no recognisable file extension
1085 1038 * (e.g. SharePoint, signed S3 URLs, or "download?id=…" endpoints).
1086 1039 *
1087 - * Uses the shared remote-fetch policy to block non-public destinations,
1040 + * Uses wp_safe_remote_get() to block requests to private/loopback addresses,
1088 1041 * and streams the response to a temp file so no body data is held in memory
1089 1042 * regardless of whether the server honours the Range header.
1090 1043 *
1091 1044 * The check relies on the ZIP magic number (PK\x03\x04) that every XLSX
@@ -1102,17 +1055,16 @@
1102 1055 if ( ! $tmpfile ) {
1103 1056 return false;
1104 1057 }
1105 1058
1106 - $response = Visualizer_Remote_Fetch::request(
1059 + $response = wp_safe_remote_get(
1107 1060 $url,
1108 1061 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,
1062 + 'timeout' => 10,
1063 + 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ),
1064 + 'headers' => array( 'Range' => 'bytes=0-3' ),
1065 + 'stream' => true,
1066 + 'filename' => $tmpfile,
1115 1067 )
1116 1068 );
1117 1069
1118 1070 if ( is_wp_error( $response ) ) {
@@ -1277,15 +1229,14 @@
1277 1229 public function uploadData() {
1278 1230 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1279 1231 // otherwise, assume this is a normal web request.
1280 1232 $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 1233
1283 - // validate nonce; capability check applies to web requests only, not trusted internal calls.
1234 + // validate nonce
1284 1235 if (
1285 1236 ! isset( $_GET['nonce'] ) ||
1286 1237 ! wp_verify_nonce( $_GET['nonce'], 'visualizer-upload-data' ) ||
1287 - ( $can_die && ! current_user_can( 'edit_posts' ) )
1238 + ! current_user_can( 'edit_posts' )
1288 1239 ) {
1289 1240 if ( ! $can_die ) {
1290 1241 return;
1291 1242 }
@@ -1300,9 +1251,9 @@
1300 1251 if (
1301 1252 ! $chart_id ||
1302 1253 ! $chart ||
1303 1254 $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ||
1304 - ( $can_die && ! current_user_can( 'edit_post', $chart_id ) )
1255 + ! current_user_can( 'edit_post', $chart_id )
1305 1256 ) {
1306 1257 if ( ! $can_die ) {
1307 1258 return;
1308 1259 }
@@ -1384,10 +1335,10 @@
1384 1335 if ( $source ) {
1385 1336 if ( $source->fetch() ) {
1386 1337 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1387 1338 $populate = true;
1388 - $json = self::decode_content( $content );
1389 - if ( is_array( $json ) ) {
1339 + if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {
1340 + $json = unserialize( $content );
1390 1341 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1391 1342 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1392 1343 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
1393 1344 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__ );
@@ -1451,11 +1402,12 @@
1451 1402 */
1452 1403 public function cloneChart() {
1453 1404 $chart_id = $success = false;
1454 1405 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1455 - if ( $nonce ) {
1406 + $capable = current_user_can( 'edit_posts' );
1407 + if ( $nonce && $capable ) {
1456 1408 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1457 - if ( $chart_id && self::can_edit_chart( $chart_id ) ) {
1409 + if ( $chart_id ) {
1458 1410 $chart = get_post( $chart_id );
1459 1411 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1460 1412 }
1461 1413 }
@@ -1475,9 +1427,9 @@
1475 1427 } else {
1476 1428 $post_meta = get_post_meta( $chart_id );
1477 1429 foreach ( $post_meta as $key => $value ) {
1478 1430 if ( strpos( $key, 'visualizer-' ) !== false ) {
1479 - add_post_meta( $new_chart_id, $key, self::maybe_decode_content( $value[0] ) );
1431 + add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
1480 1432 }
1481 1433 }
1482 1434 $redirect = esc_url(
1483 1435 add_query_arg(
@@ -1509,21 +1461,24 @@
1509 1461 * @access public
1510 1462 */
1511 1463 public function exportData() {
1512 1464 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 );
1465 + $capable = current_user_can( 'edit_posts' );
1466 + if ( $capable ) {
1467 + $chart_id = isset( $_GET['chart'] ) ? filter_var(
1468 + $_GET['chart'],
1469 + FILTER_VALIDATE_INT,
1470 + array(
1471 + 'options' => array(
1472 + 'min_range' => 1,
1473 + ),
1474 + )
1475 + ) : '';
1476 + if ( $chart_id ) {
1477 + $data = $this->_getDataAs( $chart_id, 'csv' );
1478 + if ( $data ) {
1479 + echo wp_send_json_success( $data );
1480 + }
1526 1481 }
1527 1482 }
1528 1483
1529 1484 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
@@ -1703,10 +1658,11 @@
1703 1658 */
1704 1659 public function saveFilter() {
1705 1660 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1706 1661
1707 - $chart_id = isset( $_GET['chart'] ) ? filter_var(
1708 - $_GET['chart'],
1662 + $chart_id = filter_input(
1663 + INPUT_GET,
1664 + 'chart',
1709 1665 FILTER_VALIDATE_INT,
1710 1666 array(
1711 1667 'options' => array(
1712 1668 'min_range' => 1,
@@ -1711,14 +1667,10 @@
1711 1667 'options' => array(
1712 1668 'min_range' => 1,
1713 1669 ),
1714 1670 )
1715 - ) : false;
1671 + );
1716 1672
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 1673 $hours = filter_input(
1722 1674 INPUT_POST,
1723 1675 'refresh',
1724 1676 FILTER_VALIDATE_FLOAT,
@@ -1746,9 +1698,9 @@
1746 1698 *
1747 1699 * @param string $base64_img Chart image.
1748 1700 * @param int $chart_id Chart ID.
1749 1701 * @param bool $save_attachment Save attachment.
1750 - * @return int Attachment ID, or 0 when no attachment was saved.
1702 + * @return attachment ID
1751 1703 */
1752 1704 public function save_chart_image( $base64_img, $chart_id, $save_attachment = true ) {
1753 1705 // Delete old chart image.
1754 1706 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
@@ -1763,15 +1715,11 @@
1763 1715 // Upload dir.
1764 1716 $upload_dir = wp_upload_dir();
1765 1717 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1766 1718
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 - }
1719 + $img = str_replace( 'data:image/png;base64,', '', $base64_img );
1720 + $img = str_replace( ' ', '+', $img );
1721 + $decoded = base64_decode( $img );
1774 1722 $filename = 'visualization-' . $chart_id . '.png';
1775 1723 $file_type = 'image/png';
1776 1724 $hashed_filename = $filename;
1777 1725