PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.7
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.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
visualizer / classes / Visualizer / Module / Chart.php

Chart.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.7, at classes/Visualizer/Module/Chart.php

1,801 lines 65.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Eugene Manuilov <eugene@manuilov.org> |
20 // +----------------------------------------------------------------------+
21 /**
22 * The module for all stuff related to getting, editing, creating and deleting charts.
23 *
24 * @category Visualizer
25 * @package Module
26 *
27 * @since 1.0.0
28 */
29 class Visualizer_Module_Chart extends Visualizer_Module {
30
31 const NAME = __CLASS__;
32
33 /**
34 * The chart object.
35 *
36 * @since 1.0.0
37 *
38 * @access private
39 * @var WP_Post
40 */
41 private $_chart;
42
43 /**
44 * Constructor.
45 *
46 * @since 1.0.0
47 *
48 * @access public
49 *
50 * @param Visualizer_Plugin $plugin The instance of the plugin.
51 */
52 public function __construct( Visualizer_Plugin $plugin ) {
53 parent::__construct( $plugin );
54 $this->_addAjaxAction( Visualizer_Plugin::ACTION_GET_CHARTS, 'getCharts' );
55 $this->_addAjaxAction( Visualizer_Plugin::ACTION_DELETE_CHART, 'deleteChart' );
56 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CREATE_CHART, 'renderChartPages' );
57 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EDIT_CHART, 'renderChartPages' );
58 $this->_addAjaxAction( Visualizer_Plugin::ACTION_UPLOAD_DATA, 'uploadData' );
59 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CLONE_CHART, 'cloneChart' );
60 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EXPORT_DATA, 'exportData' );
61
62 $this->_addAjaxAction( Visualizer_Plugin::ACTION_FETCH_DB_DATA, 'getQueryData' );
63 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_DB_QUERY, 'saveQuery' );
64
65 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_GET_ROOTS, 'getJsonRoots' );
66 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_GET_DATA, 'getJsonData' );
67 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_SET_DATA, 'setJsonData' );
68 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE, 'setJsonSchedule' );
69
70 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY, 'saveFilter' );
71
72 $this->_addFilter( 'visualizer_get_sidebar', 'getSidebar', 10, 2 );
73 }
74
75 /**
76 * Generates the HTML of the sidebar for the chart.
77 *
78 * @since ?
79 *
80 * @access public
81 */
82 public function getSidebar( $sidebar, $chart_id ) {
83 $chart = get_post( $chart_id );
84 $data = $this->_getChartArray( $chart );
85 $sidebar = '';
86 $sidebar_class = $this->load_chart_class_name( $chart_id );
87 if ( class_exists( $sidebar_class, true ) ) {
88 $sidebar = new $sidebar_class( $data['settings'] );
89 $sidebar->__series = $data['series'];
90 $sidebar->__data = $data['data'];
91 } else {
92 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
93 if ( $sidebar !== '' ) {
94 $sidebar->__series = $data['series'];
95 $sidebar->__data = $data['data'];
96 }
97 }
98 return str_replace( "'", '"', $sidebar->__toString() );
99 }
100
101 /**
102 * Sets the schedule for how JSON-endpoint charts should be updated.
103 *
104 * @since ?
105 *
106 * @access public
107 */
108 public function setJsonSchedule() {
109 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION, 'security' );
110
111 $chart_id = isset( $_POST['chart'] ) ? filter_var(
112 $_POST['chart'],
113 FILTER_VALIDATE_INT,
114 array(
115 'options' => array(
116 'min_range' => 1,
117 ),
118 )
119 ) : false;
120
121 if ( ! $chart_id ) {
122 wp_send_json_error();
123 }
124
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 $time = filter_input(
130 INPUT_POST,
131 'time',
132 FILTER_VALIDATE_INT,
133 array(
134 'options' => array(
135 'min_range' => -1,
136 ),
137 )
138 );
139
140 if ( Visualizer_Module::is_pro() ) {
141 $is_woocommerce_report = filter_input(
142 INPUT_POST,
143 'is_woocommerce_report',
144 FILTER_VALIDATE_BOOLEAN
145 );
146
147 if ( $is_woocommerce_report ) {
148 update_post_meta( $chart_id, Visualizer_Plugin::CF_IS_WOOCOMMERCE_SOURCE, true );
149 } else {
150 delete_post_meta( $chart_id, Visualizer_Plugin::CF_IS_WOOCOMMERCE_SOURCE );
151 }
152 }
153
154 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE );
155
156 if ( -1 < $time ) {
157 add_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, $time );
158 // Update schedules.
159 $schedules = get_option( Visualizer_Plugin::CF_JSON_SCHEDULE, array() );
160 $schedules[ $chart_id ] = time() + $time * HOUR_IN_SECONDS;
161 update_option( Visualizer_Plugin::CF_JSON_SCHEDULE, $schedules );
162 }
163 wp_send_json_success();
164 }
165
166 /**
167 * Get the root elements for JSON-endpoint.
168 *
169 * @since ?
170 *
171 * @access public
172 */
173 public function getJsonRoots() {
174 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION, 'security' );
175
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 $params = wp_parse_args( $_POST['params'] );
181
182 $source = new Visualizer_Source_Json( $params );
183
184 $roots = $source->fetchRoots();
185 if ( empty( $roots ) ) {
186 wp_send_json_error( array( 'msg' => $source->get_error() ) );
187 }
188
189 wp_send_json_success( array( 'url' => $params['url'], 'roots' => $roots ) );
190 }
191
192 /**
193 * Get the data for the JSON-endpoint corresponding to the chosen root.
194 *
195 * @since ?
196 *
197 * @access public
198 */
199 public function getJsonData() {
200 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION, 'security' );
201
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 $params = wp_parse_args( $_POST['params'] );
207
208 $chart_id = $params['chart'];
209
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 ) ) {
212 wp_die();
213 }
214
215 $source = new Visualizer_Source_Json( $params );
216 $source->fetch();
217 $data = $source->getRawData();
218
219 if ( empty( $data ) ) {
220 wp_send_json_error( array( 'msg' => esc_html__( 'Unable to fetch data from the endpoint. Please try again.', 'visualizer' ) ) );
221 }
222
223 $data = Visualizer_Render_Layout::show( 'editor-table', $data, $chart_id, 'viz-json-table', false, false );
224 wp_send_json_success( array( 'table' => $data, 'root' => $params['root'], 'url' => $params['url'], 'paging' => $source->getPaginationElements() ) );
225 }
226
227 /**
228 * Updates the database with the correct post parameters for JSON-endpoint charts.
229 *
230 * @since ?
231 *
232 * @access public
233 */
234 public function setJsonData() {
235 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_DATA . Visualizer_Plugin::VERSION, 'security' );
236
237 $params = $_POST;
238 $chart_id = isset( $_GET['chart'] ) ? absint( $_GET['chart'] ) : 0;
239
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 ) );
242 }
243
244 $chart = get_post( $chart_id );
245
246 $source = new Visualizer_Source_Json( $params );
247 update_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true );
248 $source->fetchFromEditableTable();
249
250 $content = $source->getData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
251 $chart->post_content = $content;
252 wp_update_post( $chart->to_array() );
253 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
254 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
255 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
256 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_URL, $params['url'] );
257 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_ROOT, $params['root'] );
258
259 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_HEADERS );
260 $headers = array( 'method' => $params['method'] );
261 if ( ! empty( $params['auth'] ) ) {
262 $headers['auth'] = $params['auth'];
263 } elseif ( ! empty( $params['username'] ) && ! empty( $params['password'] ) ) {
264 $headers['auth'] = array( 'username' => $params['username'], 'password' => $params['password'] );
265 }
266
267 add_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_HEADERS, $headers );
268
269 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_PAGING );
270 if ( ! empty( $params['paging'] ) ) {
271 add_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_PAGING, $params['paging'] );
272 }
273
274 if ( Visualizer_Module::is_pro() ) {
275 if ( ! empty( $params['vz_woo_source'] ) ) {
276 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_WOOCOMMERCE_SOURCE, $params['vz_woo_source'] );
277 } else {
278 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_WOOCOMMERCE_SOURCE );
279 }
280 }
281
282 $time = filter_input(
283 INPUT_POST,
284 'time',
285 FILTER_VALIDATE_INT,
286 array(
287 'options' => array(
288 'min_range' => -1,
289 ),
290 )
291 );
292
293 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE );
294
295 if ( -1 < $time ) {
296 add_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, $time );
297 }
298
299 // delete other source specific parameters.
300 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
301 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
302 delete_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_URL );
303 delete_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_SCHEDULE );
304
305 $render = new Visualizer_Render_Page_Update();
306 $render->id = $chart->ID;
307 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
308 $render->series = json_encode( $source->getSeries() );
309 $render->render();
310
311 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
312 }
313
314
315 /**
316 * Fetches charts from database.
317 *
318 * This method is also called from the media pop-up (classic editor: create a post and add chart from insert content).
319 *
320 * @since 1.0.0
321 *
322 * @access public
323 */
324 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 $query_args = array(
332 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
333 'posts_per_page' => 9,
334 'paged' => filter_input(
335 INPUT_GET,
336 'page',
337 FILTER_VALIDATE_INT,
338 array(
339 'options' => array(
340 'min_range' => 1,
341 'default' => 1,
342 ),
343 )
344 ),
345 );
346 if ( ! current_user_can( 'edit_others_posts' ) ) {
347 $query_args['author'] = get_current_user_id();
348 }
349 $filter = filter_input( INPUT_GET, 's', FILTER_SANITIZE_STRING );
350 if ( empty( $filter ) ) {
351 // 'filter' is from the modal from the add media button.
352 $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
353 }
354
355 if ( $filter && in_array( $filter, Visualizer_Plugin::getChartTypes(), true ) ) {
356 $query_args['meta_query'] = array(
357 array(
358 'key' => Visualizer_Plugin::CF_CHART_TYPE,
359 'value' => $filter,
360 'compare' => '=',
361 ),
362 );
363 }
364 $query = new WP_Query( $query_args );
365 $charts = array();
366 while ( $query->have_posts() ) {
367 $chart = $query->next_post();
368 $chart_data = $this->_getChartArray( $chart );
369 $chart_data['id'] = $chart->ID;
370 $chart_data['library'] = $this->load_chart_type( $chart->ID );
371 $css = '';
372 $settings = $chart_data['settings'];
373 $arguments = $this->get_inline_custom_css( 'visualizer-chart-' . $chart->ID, $settings );
374 if ( ! empty( $arguments ) ) {
375 $css = $arguments[0];
376 $settings = $arguments[1];
377 }
378 $chart_data['settings'] = $settings;
379 $chart_data['css'] = $css;
380 $charts[] = $chart_data;
381 }
382 self::_sendResponse(
383 array(
384 'success' => true,
385 'data' => $charts,
386 'total' => $query->max_num_pages,
387 )
388 );
389 }
390
391 /**
392 * Returns chart data required for rendering.
393 *
394 * @since 1.0.0
395 *
396 * @access private
397 *
398 * @param WP_Post|null $chart The chart object.
399 *
400 * @return array The array of chart data.
401 */
402 private function _getChartArray( $chart = null ) {
403 if ( is_null( $chart ) ) {
404 $chart = $this->_chart;
405 }
406 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
407 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ), $chart->ID, $type );
408 $data = self::get_chart_data( $chart, $type );
409 $library = $this->load_chart_type( $chart->ID );
410
411 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
412 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
413 if ( ! empty( $atts['settings'] ) ) {
414 $settings = apply_filters( $atts['settings'], $settings, $chart->ID, $type );
415 }
416
417 $css = '';
418 $arguments = $this->get_inline_custom_css( 'visualizer-' . $chart->ID, $settings );
419 if ( ! empty( $arguments ) ) {
420 $css = $arguments[0];
421 $settings = $arguments[1];
422 }
423
424 $date_formats = Visualizer_Source::get_date_formats_if_exists( $series, $data );
425
426 $code = '';
427 if ( 'd3' === $library ) {
428 $code = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
429 }
430
431 return array(
432 'type' => $type,
433 'series' => $series,
434 'settings' => $settings,
435 'data' => $data,
436 'library' => $library,
437 'code' => $code,
438 'css' => $css,
439 'date_formats' => $date_formats,
440 );
441 }
442
443 /**
444 * Sends json response.
445 *
446 * @since 1.0.0
447 *
448 * @access private
449 *
450 * @param array $results The response array.
451 */
452 public static function _sendResponse( $results ) {
453 header( 'Content-type: application/json' );
454 nocache_headers();
455 echo json_encode( $results );
456 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
457 }
458
459 /**
460 * Deletes a chart from database.
461 *
462 * @since 1.0.0
463 * @uses wp_delete_post() To delete a chart.
464 *
465 * @access public
466 */
467 public function deleteChart() {
468 $is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
469 $input = $is_post ? $_POST : $_GET;
470 $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'],
475 FILTER_VALIDATE_INT,
476 array(
477 'options' => array(
478 'min_range' => 1,
479 ),
480 )
481 ) : false;
482 if ( $chart_id ) {
483 $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 );
490 }
491 }
492 if ( $success ) {
493 global $sitepress;
494 if ( Visualizer_Module::is_pro() && ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) ) {
495 $trid = $sitepress->get_element_trid( $chart_id, 'post_' . Visualizer_Plugin::CPT_VISUALIZER );
496 $translations = $sitepress->get_element_translations( $trid );
497 if ( ! empty( $translations ) ) {
498 foreach ( $translations as $translated_post ) {
499 wp_delete_post( $translated_post->element_id, true );
500 }
501 } else {
502 wp_delete_post( $chart_id, true );
503 }
504 } else {
505 wp_delete_post( $chart_id, true );
506 }
507 }
508 if ( $is_post ) {
509 self::_sendResponse(
510 array(
511 'success' => $success,
512 )
513 );
514 }
515 wp_redirect( remove_query_arg( 'vaction', wp_get_referer() ) );
516 exit;
517 }
518
519 /**
520 * Delete charts that are still in auto-draft mode.
521 */
522 private function deleteOldCharts() {
523 $query = new WP_Query(
524 array(
525 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
526 'post_status' => 'auto-draft',
527 'fields' => 'ids',
528 'update_post_meta_cache' => false,
529 'update_post_term_cache' => false,
530 'posts_per_page' => 50,
531 'date_query' => array(
532 array(
533 'before' => 'today',
534 ),
535 ),
536 )
537 );
538
539 if ( $query->have_posts() ) {
540 $ids = array();
541 while ( $query->have_posts() ) {
542 wp_delete_post( $query->next_post(), true );
543 }
544 }
545 }
546
547 /**
548 * Renders appropriate page for chart builder. Creates new auto draft chart
549 * if no chart has been specified.
550 *
551 * @since 1.0.0
552 *
553 * @access public
554 */
555 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 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
561 if ( ! defined( 'ET_BUILDER_PRODUCT_VERSION' ) && function_exists( 'et_get_theme_version' ) ) {
562 define( 'ET_BUILDER_PRODUCT_VERSION', et_get_theme_version() );
563 }
564 // Set current screen for the render chart.
565 set_current_screen( 'visualizer_render_chart' );
566 // check chart, if chart not exists, will create new one and redirects to the same page with proper chart id
567 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
568 if ( ! empty( $_POST ) ) {
569 $_POST = map_deep( $_POST, 'wp_strip_all_tags' );
570 }
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 ) {
576 if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
577 $this->deleteOldCharts();
578 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
579 $chart_status = Visualizer_Module_Admin::checkChartStatus( $default_type );
580 if ( ! $chart_status ) {
581 $default_type = 'line';
582 }
583 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $default_type . '.csv' );
584 $source->fetch();
585 $chart_id = wp_insert_post(
586 array(
587 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
588 'post_title' => 'Visualization',
589 'post_author' => get_current_user_id(),
590 'post_status' => 'auto-draft',
591 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
592 )
593 );
594 if ( $chart_id && ! is_wp_error( $chart_id ) ) {
595 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, $default_type );
596 add_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 1 );
597 add_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
598 add_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
599 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, '' );
600 add_post_meta(
601 $chart_id,
602 Visualizer_Plugin::CF_SETTINGS,
603 array(
604 'focusTarget' => 'datum',
605 )
606 );
607
608 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
609 }
610 } 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 );
627
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] ) );
636 }
637 }
638 }
639 }
640 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
641 }
642 wp_redirect( esc_url_raw( add_query_arg( 'chart', (int) $chart_id ) ) );
643
644 if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
645 wp_die();
646 }
647 exit();
648 }
649
650 $_POST['save_chart_image'] = isset( $_POST['save_chart_image'] ) && 'yes' === $_POST['save_chart_image'] ? true : false;
651 $_POST['lazy_load_chart'] = isset( $_POST['lazy_load_chart'] ) && 'yes' === $_POST['lazy_load_chart'] ? true : false;
652
653 if ( isset( $_POST['chart-img'] ) && ! empty( $_POST['chart-img'] ) ) {
654 $attachment_id = $this->save_chart_image( $_POST['chart-img'], $chart_id, $_POST['save_chart_image'] );
655 if ( $attachment_id ) {
656 update_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, $attachment_id );
657 }
658 }
659 $lib = $this->load_chart_type( $chart_id );
660
661 // the alpha color picker (RGBA) is not supported by google.
662 $color_picker_dep = 'wp-color-picker';
663 if ( in_array( $lib, array( 'chartjs', 'datatables' ), true ) && ! wp_script_is( 'wp-color-picker-alpha', 'registered' ) ) {
664 wp_register_script( 'wp-color-picker-alpha', VISUALIZER_ABSURL . 'js/lib/wp-color-picker-alpha.min.js', array( 'wp-color-picker' ), Visualizer_Plugin::VERSION );
665 $color_picker_dep = 'wp-color-picker-alpha';
666 }
667
668 // enqueue and register scripts and styles
669 wp_register_script( 'visualizer-chosen', VISUALIZER_ABSURL . 'js/lib/chosen.jquery.min.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
670 wp_register_style( 'visualizer-chosen', VISUALIZER_ABSURL . 'css/lib/chosen.min.css', array(), Visualizer_Plugin::VERSION );
671
672 wp_register_style( 'visualizer-frame', VISUALIZER_ABSURL . 'css/frame.css', array( 'visualizer-chosen' ), Visualizer_Plugin::VERSION );
673 wp_register_script( 'visualizer-frame', VISUALIZER_ABSURL . 'js/frame.js', array( 'visualizer-chosen', 'jquery-ui-accordion', 'jquery-ui-tabs' ), Visualizer_Plugin::VERSION, true );
674 wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
675 wp_register_script(
676 'visualizer-render',
677 VISUALIZER_ABSURL . 'js/render-facade.js',
678 apply_filters( 'visualizer_assets_render', array( 'visualizer-frame', 'visualizer-customization' ), false ),
679 Visualizer_Plugin::VERSION,
680 true
681 );
682 wp_register_script(
683 'visualizer-preview',
684 VISUALIZER_ABSURL . 'js/preview.js',
685 array(
686 $color_picker_dep,
687 'visualizer-render',
688 ),
689 Visualizer_Plugin::VERSION,
690 true
691 );
692 wp_register_script( 'visualizer-editor-simple', VISUALIZER_ABSURL . 'js/simple-editor.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
693
694 do_action( 'visualizer_add_scripts' );
695
696 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
697 global $Visualizer_Pro;
698 $Visualizer_Pro->_addScriptsAndStyles();
699 }
700
701 // dispatch pages
702 $this->_chart = get_post( $chart_id );
703 $tab = isset( $_GET['tab'] ) && ! empty( $_GET['tab'] ) ? $_GET['tab'] : 'visualizer';
704
705 // skip chart type pages only for existing charts.
706 if ( VISUALIZER_SKIP_CHART_TYPE_PAGE && 'auto-draft' !== $this->_chart->post_status && ( ! empty( $_GET['tab'] ) && 'visualizer' === $_GET['tab'] ) ) {
707 $tab = 'settings';
708 }
709
710 if ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) {
711 // if the cancel button is clicked.
712 $this->undoRevisions( $chart_id, true );
713 } elseif ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
714 $this->handlePermissions();
715 // if the save button is clicked.
716 $this->undoRevisions( $chart_id, false );
717 } else {
718 // if the edit button is clicked.
719 $this->_chart = $this->handleExistingRevisions( $chart_id, $this->_chart );
720 }
721
722 // Clear existing chart cache.
723 if ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
724 $cache_key = Visualizer_Plugin::CF_CHART_CACHE . '_' . $chart_id;
725 if ( get_transient( $cache_key ) ) {
726 delete_transient( $cache_key );
727 }
728 }
729
730 switch ( $tab ) {
731 case 'settings':
732 $this->_handleDataAndSettingsPage();
733 break;
734 case 'type': // fall through.
735 case 'visualizer': // fall through.
736 $this->_handleTypesPage();
737 break;
738 default:
739 // this should never happen.
740 break;
741 }
742 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
743 }
744
745 /**
746 * Load code editor assets.
747 */
748 private function loadCodeEditorAssets( $chart_id ) {
749 global $wp_version;
750
751 $wp_scripts = wp_scripts();
752
753 // data tables assets.
754 wp_register_script( 'visualizer-datatables', VISUALIZER_ABSURL . 'js/lib/datatables.min.js', array( 'jquery-ui-core' ), Visualizer_Plugin::VERSION );
755 wp_register_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION );
756 wp_register_style( 'visualizer-jquery-ui', sprintf( '//code.jquery.com/ui/%s/themes/smoothness/jquery-ui.css', $wp_scripts->registered['jquery-ui-core']->ver ), array( 'visualizer-datatables' ), Visualizer_Plugin::VERSION );
757 wp_enqueue_script( 'visualizer-datatables' );
758 wp_enqueue_style( 'visualizer-jquery-ui' );
759
760 if ( ! Visualizer_Module::is_pro() ) {
761 return;
762 }
763
764 $table_col_mapping = Visualizer_Source_Query_Params::get_all_db_tables_column_mapping( $chart_id );
765
766 if ( version_compare( $wp_version, '4.9.0', '<' ) ) {
767 // code mirror assets.
768 wp_register_script( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
769 wp_register_script( 'visualizer-codemirror-placeholder', '//codemirror.net/addon/display/placeholder.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
770 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
771 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
772 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
773 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 );
775 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
776 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
777
778 wp_enqueue_script( 'visualizer-codemirror-hint' );
779 wp_enqueue_style( 'visualizer-codemirror-hint' );
780 } else {
781 wp_enqueue_code_editor(
782 array(
783 'type' => 'sql',
784 'codemirror' => array(
785 'autofocus' => true,
786 'lineWrapping' => true,
787 'dragDrop' => false,
788 'matchBrackets' => true,
789 'autoCloseBrackets' => true,
790 'extraKeys' => array( 'Shift-Space' => 'autocomplete' ),
791 'hintOptions' => array( 'tables' => $table_col_mapping ),
792 ),
793 )
794 );
795 }
796
797 return $table_col_mapping;
798 }
799
800 /**
801 * Handle permissions from the new conslidated settings sidebar.
802 */
803 private function handlePermissions() {
804 if ( ! Visualizer_Module::is_pro() ) {
805 return;
806 }
807
808 // we will not support old free and new pro.
809 // handling new free and old pro.
810 if ( has_action( 'visualizer_pro_handle_tab' ) ) {
811 do_action( 'visualizer_pro_handle_tab', 'permissions', $this->_chart );
812 } else {
813 do_action( 'visualizer_handle_permissions', $this->_chart );
814 }
815 }
816
817 /**
818 * Handle data and settings page
819 */
820 private function _handleDataAndSettingsPage() {
821 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
822 $is_canceled = isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] );
823 $is_newly_created = $this->_chart->post_status === 'auto-draft';
824
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 if ( $is_newly_created && ! $is_canceled ) {
830 $this->_chart->post_status = 'publish';
831
832 // 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.
833 // we do not want any difference in data so disable revisions temporarily.
834 $this->disableRevisionsTemporarily();
835
836 wp_update_post( $this->_chart->to_array() );
837 }
838 // save meta data only when it is NOT being canceled.
839 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 );
847
848 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
849 // this will help in searching with the chart id.
850 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
851 $title = null;
852 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
853 $title = $settings['title'];
854 if ( is_array( $title ) ) {
855 $title = $settings['title']['text'];
856 }
857 }
858 if ( empty( $title ) ) {
859 $title = $this->_chart->ID;
860 }
861 $settings['internal_title'] = $title;
862 $settings_label = isset( $settings['pieResidueSliceLabel'] ) ? $settings['pieResidueSliceLabel'] : '';
863 if ( empty( $settings_label ) ) {
864 $settings['pieResidueSliceLabel'] = esc_html__( 'Other', 'visualizer' );
865 } else {
866 $settings['pieResidueSliceLabel'] = $settings_label;
867 }
868 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
869 }
870 $render = new Visualizer_Render_Page_Send();
871 $render->text = sprintf( '[visualizer id="%d"]', $this->_chart->ID );
872 wp_iframe( array( $render, 'render' ) );
873 return;
874 }
875 $data = $this->_getChartArray();
876 $sidebar = '';
877 $sidebar_class = $this->load_chart_class_name( $this->_chart->ID );
878 if ( class_exists( $sidebar_class, true ) ) {
879 $sidebar = new $sidebar_class( $data['settings'] );
880 $sidebar->__series = $data['series'];
881 $sidebar->__data = $data['data'];
882 } else {
883 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
884 if ( $sidebar !== '' ) {
885 $sidebar->__series = $data['series'];
886 $sidebar->__data = $data['data'];
887 }
888 }
889 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
890 wp_enqueue_style( 'wp-color-picker' );
891 wp_enqueue_style( 'visualizer-frame' );
892 wp_enqueue_script( 'visualizer-preview' );
893 wp_enqueue_script( 'visualizer-chosen' );
894 wp_enqueue_script( 'visualizer-render' );
895 wp_enqueue_code_editor( array( 'type' => 'application/json' ) );
896
897 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
898 wp_enqueue_script( 'visualizer-editor-simple' );
899 wp_localize_script(
900 'visualizer-editor-simple',
901 'visualizer1',
902 array(
903 'ajax' => array(
904 'url' => admin_url( 'admin-ajax.php' ),
905 'nonces' => array(),
906 'actions' => array(),
907 ),
908 )
909 );
910 }
911
912 $table_col_mapping = $this->loadCodeEditorAssets( $this->_chart->ID );
913
914 wp_localize_script(
915 'visualizer-render',
916 'visualizer',
917 array(
918 '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' ),
926 ),
927 'charts' => array(
928 'canvas' => $data,
929 'id' => $this->_chart->ID,
930 ),
931 'language' => $this->get_language(),
932 'map_api_key' => get_option( 'visualizer-map-api-key' ),
933 'ajax' => array(
934 'url' => admin_url( 'admin-ajax.php' ),
935 'nonces' => array(
936 'permissions' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA ),
937 'db_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION ),
938 'json_get_roots' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION ),
939 'json_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION ),
940 'json_set_schedule' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION ),
941 ),
942 'actions' => array(
943 'permissions' => Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA,
944 'db_get_data' => Visualizer_Plugin::ACTION_FETCH_DB_DATA,
945 'json_get_roots' => Visualizer_Plugin::ACTION_JSON_GET_ROOTS,
946 'json_get_data' => Visualizer_Plugin::ACTION_JSON_GET_DATA,
947 'json_set_schedule' => Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE,
948 ),
949 ),
950 'db_query' => array(
951 'tables' => $table_col_mapping,
952 ),
953 'is_pro' => Visualizer_Module::is_pro(),
954 'page_type' => 'chart',
955 'json_tag_separator' => Visualizer_Source_Json::TAG_SEPARATOR,
956 'json_tag_separator_view' => Visualizer_Source_Json::TAG_SEPARATOR_VIEW,
957 'is_front' => false,
958 'rest_base' => get_rest_url( null, 'wc/v3/reports/' ),
959 )
960 );
961
962 $render = new Visualizer_Render_Page_Data();
963 $render->chart = $this->_chart;
964 $render->type = $data['type'];
965 $render->custom_css = $data['css'];
966 $render->sidebar = $sidebar;
967 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
968 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
969 ? esc_html__( 'Save Chart', 'visualizer' )
970 : esc_html__( 'Create Chart', 'visualizer' );
971
972 $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
973 } else {
974 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
975 }
976
977 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
978
979 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
980 global $Visualizer_Pro;
981 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
982 }
983
984 $this->_addAction( 'admin_head', 'renderFlattrScript' );
985 wp_iframe( array( $render, 'render' ) );
986 }
987
988 /**
989 * Handles chart type selection page.
990 *
991 * @since 1.0.0
992 *
993 * @access private
994 */
995 private function _handleTypesPage() {
996 // process post request
997 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ), 'visualizer-upload-data' ) ) {
998 $type = filter_input( INPUT_POST, 'type' );
999 $library = filter_input( INPUT_POST, 'chart-library' );
1000 if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
1001 if ( empty( $library ) ) {
1002 // library cannot be empty.
1003 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Chart library empty while creating the chart! Aborting...', 'error', __FILE__, __LINE__ );
1004 return;
1005 }
1006
1007 // save new chart type
1008 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, $type );
1009 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, $library );
1010 // if the chart has default data, update it with appropriate default data for new type
1011 if ( filter_var( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, true ), FILTER_VALIDATE_BOOLEAN ) ) {
1012 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $type . '.csv' );
1013 $source->fetch();
1014 $this->_chart->post_content = $source->getData( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1015 wp_update_post( $this->_chart->to_array() );
1016 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1017 }
1018
1019 Visualizer_Module_Utility::set_defaults( $this->_chart );
1020
1021 // redirect to next tab
1022 // changed by Ash/Upwork
1023 wp_redirect( esc_url_raw( add_query_arg( 'tab', 'settings' ) ) );
1024
1025 return;
1026 }
1027 }
1028 $render = new Visualizer_Render_Page_Types();
1029 $render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
1030 $render->types = Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' );
1031 $render->chart = $this->_chart;
1032 wp_enqueue_style( 'visualizer-frame' );
1033 wp_enqueue_script( 'visualizer-frame' );
1034 wp_iframe( array( $render, 'render' ) );
1035 }
1036
1037 /**
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 * Renders flattr script in the iframe <head>
1066 *
1067 * @since 1.4.2
1068 * @action admin_head
1069 *
1070 * @access public
1071 */
1072 public function renderFlattrScript() {
1073 echo '';
1074 }
1075
1076 /**
1077 * Processes the CSV that is sent in the request as a string.
1078 *
1079 * @since 3.2.0
1080 */
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 private function handleCSVasString( $data, $editor_type ) {
1155 $source = null;
1156
1157 switch ( $editor_type ) {
1158 case 'text':
1159 // data coming in from the text editor.
1160 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1161 $handle = fopen( $tmpfile, 'w' );
1162 $values = preg_split( '/[\n\r]+/', stripslashes( trim( $data ) ) );
1163 if ( $values ) {
1164 foreach ( $values as $row ) {
1165 if ( empty( $row ) ) {
1166 continue;
1167 }
1168 $row = explode( ',', $row );
1169 $row = array_map(
1170 function ( $r ) {
1171 return '' === $r ? ' ' : $r;
1172 },
1173 $row
1174 );
1175 $row = implode( ',', $row );
1176 // don't use fpucsv here because we need to just dump the data
1177 // minus the empty rows
1178 // because fputcsv needs to tokenize
1179 // we can standardize the CSV enclosure here and replace all ' with "
1180 // we can assume that if there are an even number of ' they should be changed to "
1181 // but that will screw up words like fo'c'sle
1182 // so here let's just assume ' will NOT be used for enclosures
1183 fwrite( $handle, $row );
1184 fwrite( $handle, PHP_EOL );
1185 }
1186 }
1187 $source = new Visualizer_Source_Csv( $tmpfile );
1188 fclose( $handle );
1189 break;
1190 case 'table':
1191 // Import from Chart
1192 // fall-through.
1193 case 'excel':
1194 // data coming in from the excel editor.
1195 $source = apply_filters( 'visualizer_pro_handle_chart_data', $data, '' );
1196 break;
1197 }
1198 return $source;
1199 }
1200
1201 /**
1202 * Parses the data uploaded as an HTML table.
1203 *
1204 * @since 3.2.0
1205 *
1206 * @access private
1207 */
1208 private function handleTabularData() {
1209 $csv = array();
1210 // the datatable mentions the headers twice, so lets remove the duplicates.
1211 $headers = array_unique( array_filter( $_POST['header'] ) );
1212 $types = $_POST['type'];
1213
1214 // capture all the indexes that correspond to excluded columns.
1215 $exclude = array();
1216 $index = 0;
1217 foreach ( $types as $type ) {
1218 if ( empty( $type ) ) {
1219 $exclude[] = $index;
1220 }
1221 ++$index;
1222 }
1223
1224 // when N headers are being renamed, the number of headers increases by N
1225 // because of the way datatable duplicates header information
1226 // so unset the headers that have been renamed.
1227 if ( count( $headers ) !== count( $types ) ) {
1228 $to = count( $headers );
1229 for ( $i = count( $types ); $i < $to; $i++ ) {
1230 unset( $headers[ $i + 1 ] );
1231 }
1232 }
1233
1234 $columns = array();
1235 for ( $i = 0; $i < count( $headers ); $i++ ) {
1236 if ( ! isset( $_POST[ 'data' . $i ] ) ) {
1237 continue;
1238 }
1239 $columns[ $i ] = $_POST[ 'data' . $i ];
1240 }
1241
1242 $csv[] = $headers;
1243 $csv[] = $types;
1244 for ( $j = 0; $j < count( $columns[0] ); $j++ ) {
1245 $row = array();
1246 for ( $i = 0; $i < count( $headers ); $i++ ) {
1247 $row[] = sanitize_text_field( $columns[ $i ][ $j ] );
1248 }
1249 $csv[] = $row;
1250 }
1251
1252 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1253 $handle = fopen( $tmpfile, 'w' );
1254
1255 if ( $csv ) {
1256 $index = 0;
1257 foreach ( $csv as $row ) {
1258 // remove all the cells corresponding to the excluded headers.
1259 foreach ( $exclude as $j ) {
1260 unset( $row[ $j ] );
1261 }
1262 fputcsv( $handle, $row );
1263 }
1264 }
1265 $source = new Visualizer_Source_Csv( $tmpfile );
1266 fclose( $handle );
1267 return $source;
1268 }
1269
1270 /**
1271 * Parses uploaded CSV file and saves new data for the chart.
1272 *
1273 * @since 1.0.0
1274 *
1275 * @access public
1276 */
1277 public function uploadData() {
1278 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1279 // otherwise, assume this is a normal web request.
1280 $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
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 ) {
1289 if ( ! $can_die ) {
1290 return;
1291 }
1292 status_header( 403 );
1293 exit;
1294 }
1295
1296 // check chart, if chart exists
1297 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1298 $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 ) {
1306 if ( ! $can_die ) {
1307 return;
1308 }
1309 status_header( 400 );
1310 exit;
1311 }
1312
1313 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Uploading data for chart %d with POST = %s and GET = %s', $chart_id, print_r( $_POST, true ), print_r( $_GET, true ) ), 'debug', __FILE__, __LINE__ );
1314
1315 if ( ! isset( $_POST['vz-import-time'] ) ) {
1316 apply_filters( 'visualizer_pro_remove_schedule', $chart_id );
1317 }
1318
1319 if ( ! isset( $_POST['chart_data_src'] ) || Visualizer_Plugin::CF_SOURCE_FILTER !== $_POST['chart_data_src'] ) {
1320 // delete the filters in case this chart is being uploaded from other data sources
1321 delete_post_meta( $chart_id, Visualizer_Plugin::CF_FILTER_CONFIG );
1322 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_FILTER_CONFIG );
1323 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_DB_QUERY );
1324
1325 // delete "import from db" specific parameters.
1326 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
1327 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
1328 delete_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS );
1329 }
1330
1331 // delete json related data.
1332 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL );
1333 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT );
1334 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING );
1335 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_HEADERS );
1336
1337 // delete last error
1338 delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
1339
1340 // delete editor related data.
1341 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR );
1342
1343 // delete this so that a JSON import can be later edited manually without a problem.
1344 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE );
1345
1346 $source = null;
1347 $render = new Visualizer_Render_Page_Update();
1348
1349 $remote_data = false;
1350 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1351 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1352 }
1353 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 }
1360 if ( isset( $_POST['vz-import-time'] ) ) {
1361 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1362 }
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 }
1370 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1371 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1372 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1373 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
1374 $source = $this->handleTabularData();
1375 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1376 } else {
1377 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' ) );
1380 }
1381
1382 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
1384 if ( $source ) {
1385 if ( $source->fetch() ) {
1386 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1387 $populate = true;
1388 $json = self::decode_content( $content );
1389 if ( is_array( $json ) ) {
1390 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1391 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1392 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
1393 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__ );
1394 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Not populating chart data as source exists (%s) but data is empty!', $json['source'] ) );
1395 $populate = false;
1396 }
1397 }
1398 if ( $populate ) {
1399 $chart->post_content = $content;
1400 }
1401 wp_update_post( $chart->to_array() );
1402
1403 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1404 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1405 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1406
1407 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Updated post for chart %d', $chart_id ), 'debug', __FILE__, __LINE__ );
1408
1409 Visualizer_Module_Utility::set_defaults( $chart, null );
1410
1411 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1412 if ( isset( $settings['series'] ) && ! ( count( $settings['series'] ) - count( $source->getSeries() ) > 1 ) ) {
1413 $diff_total_series = abs( count( $settings['series'] ) - count( $source->getSeries() ) );
1414 if ( $diff_total_series ) {
1415 foreach ( range( 1, $diff_total_series ) as $k => $diff_series ) {
1416 $settings['series'][] = end( $settings['series'] );
1417 }
1418 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
1419 }
1420 }
1421
1422 $render->id = $chart->ID;
1423 $render->data = json_encode( $source->getRawData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1424 $render->series = json_encode( $source->getSeries() );
1425 $render->settings = json_encode( $settings );
1426 } else {
1427 $error = $source->get_error();
1428 if ( empty( $error ) ) {
1429 $error = esc_html__( 'The CSV file couldn\'t be read. Check that it\'s properly formatted and try again.', 'visualizer' );
1430 }
1431 $render->message = $error;
1432 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1433 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
1434 }
1435 } else {
1436 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unknown internal error for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1437 }
1438 $render->render();
1439 if ( ! $can_die ) {
1440 return;
1441 }
1442 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1443 }
1444
1445 /**
1446 * Clones the chart.
1447 *
1448 * @since 1.0.0
1449 *
1450 * @access public
1451 */
1452 public function cloneChart() {
1453 $chart_id = $success = false;
1454 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1455 if ( $nonce ) {
1456 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1457 if ( $chart_id && self::can_edit_chart( $chart_id ) ) {
1458 $chart = get_post( $chart_id );
1459 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1460 }
1461 }
1462 $redirect = remove_query_arg( 'vaction', wp_get_referer() );
1463 if ( $success ) {
1464 $new_chart_id = wp_insert_post(
1465 array(
1466 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1467 'post_title' => 'Visualization',
1468 'post_author' => get_current_user_id(),
1469 'post_status' => $chart->post_status,
1470 'post_content' => $chart->post_content,
1471 )
1472 );
1473 if ( is_wp_error( $new_chart_id ) ) {
1474 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while cloning chart %d = %s', $chart_id, print_r( $new_chart_id, true ) ), 'error', __FILE__, __LINE__ );
1475 } else {
1476 $post_meta = get_post_meta( $chart_id );
1477 foreach ( $post_meta as $key => $value ) {
1478 if ( strpos( $key, 'visualizer-' ) !== false ) {
1479 add_post_meta( $new_chart_id, $key, self::maybe_decode_content( $value[0] ) );
1480 }
1481 }
1482 $redirect = esc_url(
1483 add_query_arg(
1484 array(
1485 'page' => 'visualizer',
1486 'type' => filter_input( INPUT_GET, 'type' ),
1487 'vaction' => false,
1488 ),
1489 admin_url( 'admin.php' )
1490 ),
1491 null,
1492 'db'
1493 );
1494 }
1495 }
1496
1497 if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
1498 wp_die();
1499 }
1500 wp_redirect( $redirect );
1501 exit;
1502 }
1503
1504 /**
1505 * Exports the chart data
1506 *
1507 * @since 1.0.0
1508 *
1509 * @access public
1510 */
1511 public function exportData() {
1512 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 );
1526 }
1527 }
1528
1529 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1530 }
1531
1532 /**
1533 * Handles chart data page.
1534 *
1535 * @since 1.0.0
1536 *
1537 * @access private
1538 */
1539 private function _handleDataPage() {
1540 $data = $this->_getChartArray();
1541 $render = new Visualizer_Render_Page_Data();
1542 $render->chart = $this->_chart;
1543 $render->type = $data['type'];
1544
1545 if ( $data && $data['settings'] ) {
1546 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
1547 }
1548 wp_enqueue_style( 'visualizer-frame' );
1549 wp_enqueue_script( 'visualizer-render' );
1550 wp_localize_script(
1551 'visualizer-render',
1552 'visualizer',
1553 array(
1554 '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' ),
1558 ),
1559 'charts' => array(
1560 'canvas' => $data,
1561 ),
1562 )
1563 );
1564
1565 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
1566
1567 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
1568 global $Visualizer_Pro;
1569 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
1570 }
1571
1572 // Added by Ash/Upwork
1573 $this->_addAction( 'admin_head', 'renderFlattrScript' );
1574 wp_iframe( array( $render, 'render' ) );
1575 }
1576
1577 /**
1578 * Returns the data for the query.
1579 *
1580 * @access public
1581 */
1582 public function getQueryData() {
1583 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1584
1585 if ( ! current_user_can( 'administrator' ) ) {
1586 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1587 }
1588 if ( ! is_super_admin() ) {
1589 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1590 }
1591
1592 if ( ! Visualizer_Module::is_pro() ) {
1593 wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1594 }
1595
1596 $params = wp_parse_args( $_POST['params'] );
1597 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1598 $query = trim( $params['query'], ';' );
1599
1600 $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1601 $html = $source->fetch( true );
1602 $error = $source->get_error();
1603 if ( ! empty( $error ) ) {
1604 wp_send_json_error( array( 'msg' => $error ) );
1605 }
1606 wp_send_json_success( array( 'table' => $html ) );
1607 }
1608
1609 /**
1610 * Saves the query and the schedule.
1611 *
1612 * @access public
1613 */
1614 public function saveQuery() {
1615 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1616
1617 if ( ! current_user_can( 'administrator' ) ) {
1618 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1619 }
1620 if ( ! is_super_admin() ) {
1621 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1622 }
1623
1624 if ( ! Visualizer_Module::is_pro() ) {
1625 wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1626 }
1627
1628 $chart_id = filter_input(
1629 INPUT_GET,
1630 'chart',
1631 FILTER_VALIDATE_INT,
1632 array(
1633 'options' => array(
1634 'min_range' => 1,
1635 ),
1636 )
1637 );
1638
1639 $hours = filter_input(
1640 INPUT_POST,
1641 'refresh',
1642 FILTER_VALIDATE_FLOAT,
1643 array(
1644 'options' => array(
1645 'min_range' => -1,
1646 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1647 ),
1648 )
1649 );
1650
1651 if ( ! is_numeric( $hours ) ) {
1652 $hours = -1;
1653 }
1654
1655 $render = new Visualizer_Render_Page_Update();
1656 if ( $chart_id ) {
1657 $params = wp_parse_args( $_POST['params'] );
1658 $query = trim( $params['query'], ';' );
1659 $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1660 $source->fetch( false );
1661 $error = $source->get_error();
1662 if ( empty( $error ) ) {
1663 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $query ) );
1664 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1665 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1666 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1667 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1668 if ( isset( $params['db_type'] ) && $params['db_type'] !== Visualizer_Plugin::WP_DB_NAME ) {
1669 $remote_db_params = $params;
1670 unset( $remote_db_params['query'] );
1671 unset( $remote_db_params['chart_id'] );
1672 update_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS, $remote_db_params );
1673 }
1674
1675 $schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
1676 $schedules[ $chart_id ] = time() + $hours * HOUR_IN_SECONDS;
1677 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $schedules );
1678
1679 wp_update_post(
1680 array(
1681 'ID' => $chart_id,
1682 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
1683 )
1684 );
1685 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1686 $render->series = json_encode( $source->getSeries() );
1687 $render->id = $chart_id;
1688 } else {
1689 $render->message = $error;
1690 }
1691 }
1692 $render->render();
1693 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1694 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1695 }
1696 }
1697
1698
1699 /**
1700 * Saves the filter query and the schedule.
1701 *
1702 * @access public
1703 */
1704 public function saveFilter() {
1705 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1706
1707 $chart_id = isset( $_GET['chart'] ) ? filter_var(
1708 $_GET['chart'],
1709 FILTER_VALIDATE_INT,
1710 array(
1711 'options' => array(
1712 'min_range' => 1,
1713 ),
1714 )
1715 ) : false;
1716
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 $hours = filter_input(
1722 INPUT_POST,
1723 'refresh',
1724 FILTER_VALIDATE_FLOAT,
1725 array(
1726 'options' => array(
1727 'min_range' => -1,
1728 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1729 ),
1730 )
1731 );
1732
1733 if ( ! is_numeric( $hours ) ) {
1734 $hours = -1;
1735 }
1736
1737 do_action( 'visualizer_save_filter', $chart_id, $hours );
1738
1739 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1740 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1741 }
1742 }
1743
1744 /**
1745 * Save chart image.
1746 *
1747 * @param string $base64_img Chart image.
1748 * @param int $chart_id Chart ID.
1749 * @param bool $save_attachment Save attachment.
1750 * @return int Attachment ID, or 0 when no attachment was saved.
1751 */
1752 public function save_chart_image( $base64_img, $chart_id, $save_attachment = true ) {
1753 // Delete old chart image.
1754 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
1755 if ( $old_attachment_id ) {
1756 wp_delete_attachment( $old_attachment_id, true );
1757 }
1758
1759 if ( ! $save_attachment ) {
1760 return 0;
1761 }
1762
1763 // Upload dir.
1764 $upload_dir = wp_upload_dir();
1765 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1766
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 }
1774 $filename = 'visualization-' . $chart_id . '.png';
1775 $file_type = 'image/png';
1776 $hashed_filename = $filename;
1777
1778 // Save the image in the uploads directory.
1779 require_once ABSPATH . '/wp-admin/includes/file.php';
1780 \WP_Filesystem();
1781 global $wp_filesystem;
1782 if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
1783 $creds = request_filesystem_credentials( site_url() );
1784 wp_filesystem( $creds );
1785 }
1786 $upload_file = $wp_filesystem->put_contents( $upload_path . $hashed_filename, $decoded );
1787
1788 // Insert new chart image.
1789 $attachment = array(
1790 'post_mime_type' => $file_type,
1791 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $hashed_filename ) ),
1792 'post_content' => '',
1793 'post_status' => 'inherit',
1794 'guid' => $upload_dir['url'] . '/' . basename( $hashed_filename ),
1795 );
1796
1797 $attach_id = wp_insert_attachment( $attachment, $upload_dir['path'] . '/' . $hashed_filename );
1798 return $attach_id;
1799 }
1800 }
1801