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.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.5, at classes/Visualizer/Module/Chart.php

1,749 lines 62.4 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 = filter_input(
112 INPUT_POST,
113 'chart',
114 FILTER_VALIDATE_INT,
115 array(
116 'options' => array(
117 'min_range' => 1,
118 ),
119 )
120 );
121
122 if ( ! $chart_id ) {
123 wp_send_json_error();
124 }
125
126 $time = filter_input(
127 INPUT_POST,
128 'time',
129 FILTER_VALIDATE_INT,
130 array(
131 'options' => array(
132 'min_range' => -1,
133 ),
134 )
135 );
136
137 if ( Visualizer_Module::is_pro() ) {
138 $is_woocommerce_report = filter_input(
139 INPUT_POST,
140 'is_woocommerce_report',
141 FILTER_VALIDATE_BOOLEAN
142 );
143
144 if ( $is_woocommerce_report ) {
145 update_post_meta( $chart_id, Visualizer_Plugin::CF_IS_WOOCOMMERCE_SOURCE, true );
146 } else {
147 delete_post_meta( $chart_id, Visualizer_Plugin::CF_IS_WOOCOMMERCE_SOURCE );
148 }
149 }
150
151 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE );
152
153 if ( -1 < $time ) {
154 add_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, $time );
155 // Update schedules.
156 $schedules = get_option( Visualizer_Plugin::CF_JSON_SCHEDULE, array() );
157 $schedules[ $chart_id ] = time() + $time * HOUR_IN_SECONDS;
158 update_option( Visualizer_Plugin::CF_JSON_SCHEDULE, $schedules );
159 }
160 wp_send_json_success();
161 }
162
163 /**
164 * Get the root elements for JSON-endpoint.
165 *
166 * @since ?
167 *
168 * @access public
169 */
170 public function getJsonRoots() {
171 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION, 'security' );
172
173 if ( ! current_user_can( 'edit_posts' ) ) {
174 wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ) );
175 }
176
177 $params = wp_parse_args( $_POST['params'] );
178
179 $source = new Visualizer_Source_Json( $params );
180
181 $roots = $source->fetchRoots();
182 if ( empty( $roots ) ) {
183 wp_send_json_error( array( 'msg' => $source->get_error() ) );
184 }
185
186 wp_send_json_success( array( 'url' => $params['url'], 'roots' => $roots ) );
187 }
188
189 /**
190 * Get the data for the JSON-endpoint corresponding to the chosen root.
191 *
192 * @since ?
193 *
194 * @access public
195 */
196 public function getJsonData() {
197 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION, 'security' );
198
199 if ( ! current_user_can( 'edit_posts' ) ) {
200 wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this action.', 'visualizer' ) ) );
201 }
202
203 $params = wp_parse_args( $_POST['params'] );
204
205 $chart_id = $params['chart'];
206
207 if ( empty( $chart_id ) ) {
208 wp_die();
209 }
210
211 $source = new Visualizer_Source_Json( $params );
212 $source->fetch();
213 $data = $source->getRawData();
214
215 if ( empty( $data ) ) {
216 wp_send_json_error( array( 'msg' => esc_html__( 'Unable to fetch data from the endpoint. Please try again.', 'visualizer' ) ) );
217 }
218
219 $data = Visualizer_Render_Layout::show( 'editor-table', $data, $chart_id, 'viz-json-table', false, false );
220 wp_send_json_success( array( 'table' => $data, 'root' => $params['root'], 'url' => $params['url'], 'paging' => $source->getPaginationElements() ) );
221 }
222
223 /**
224 * Updates the database with the correct post parameters for JSON-endpoint charts.
225 *
226 * @since ?
227 *
228 * @access public
229 */
230 public function setJsonData() {
231 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_DATA . Visualizer_Plugin::VERSION, 'security' );
232
233 $params = $_POST;
234 $chart_id = $_GET['chart'];
235
236 if ( empty( $chart_id ) ) {
237 wp_die();
238 }
239
240 $chart = get_post( $chart_id );
241
242 $source = new Visualizer_Source_Json( $params );
243 update_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true );
244 $source->fetchFromEditableTable();
245
246 $content = $source->getData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
247 $chart->post_content = $content;
248 wp_update_post( $chart->to_array() );
249 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
250 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
251 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
252 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_URL, $params['url'] );
253 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_ROOT, $params['root'] );
254
255 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_HEADERS );
256 $headers = array( 'method' => $params['method'] );
257 if ( ! empty( $params['auth'] ) ) {
258 $headers['auth'] = $params['auth'];
259 } elseif ( ! empty( $params['username'] ) && ! empty( $params['password'] ) ) {
260 $headers['auth'] = array( 'username' => $params['username'], 'password' => $params['password'] );
261 }
262
263 add_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_HEADERS, $headers );
264
265 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_PAGING );
266 if ( ! empty( $params['paging'] ) ) {
267 add_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_PAGING, $params['paging'] );
268 }
269
270 if ( Visualizer_Module::is_pro() ) {
271 if ( ! empty( $params['vz_woo_source'] ) ) {
272 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_WOOCOMMERCE_SOURCE, $params['vz_woo_source'] );
273 } else {
274 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_WOOCOMMERCE_SOURCE );
275 }
276 }
277
278 $time = filter_input(
279 INPUT_POST,
280 'time',
281 FILTER_VALIDATE_INT,
282 array(
283 'options' => array(
284 'min_range' => -1,
285 ),
286 )
287 );
288
289 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE );
290
291 if ( -1 < $time ) {
292 add_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, $time );
293 }
294
295 // delete other source specific parameters.
296 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
297 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
298 delete_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_URL );
299 delete_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_SCHEDULE );
300
301 $render = new Visualizer_Render_Page_Update();
302 $render->id = $chart->ID;
303 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
304 $render->series = json_encode( $source->getSeries() );
305 $render->render();
306
307 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
308 }
309
310
311 /**
312 * Fetches charts from database.
313 *
314 * This method is also called from the media pop-up (classic editor: create a post and add chart from insert content).
315 *
316 * @since 1.0.0
317 *
318 * @access public
319 */
320 public function getCharts() {
321 $query_args = array(
322 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
323 'posts_per_page' => 9,
324 'paged' => filter_input(
325 INPUT_GET,
326 'page',
327 FILTER_VALIDATE_INT,
328 array(
329 'options' => array(
330 'min_range' => 1,
331 'default' => 1,
332 ),
333 )
334 ),
335 );
336 $filter = filter_input( INPUT_GET, 's', FILTER_SANITIZE_STRING );
337 if ( empty( $filter ) ) {
338 // 'filter' is from the modal from the add media button.
339 $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
340 }
341
342 if ( $filter && in_array( $filter, Visualizer_Plugin::getChartTypes(), true ) ) {
343 $query_args['meta_query'] = array(
344 array(
345 'key' => Visualizer_Plugin::CF_CHART_TYPE,
346 'value' => $filter,
347 'compare' => '=',
348 ),
349 );
350 }
351 $query = new WP_Query( $query_args );
352 $charts = array();
353 while ( $query->have_posts() ) {
354 $chart = $query->next_post();
355 $chart_data = $this->_getChartArray( $chart );
356 $chart_data['id'] = $chart->ID;
357 $chart_data['library'] = $this->load_chart_type( $chart->ID );
358 $css = '';
359 $settings = $chart_data['settings'];
360 $arguments = $this->get_inline_custom_css( 'visualizer-chart-' . $chart->ID, $settings );
361 if ( ! empty( $arguments ) ) {
362 $css = $arguments[0];
363 $settings = $arguments[1];
364 }
365 $chart_data['settings'] = $settings;
366 $chart_data['css'] = $css;
367 $charts[] = $chart_data;
368 }
369 self::_sendResponse(
370 array(
371 'success' => true,
372 'data' => $charts,
373 'total' => $query->max_num_pages,
374 )
375 );
376 }
377
378 /**
379 * Returns chart data required for rendering.
380 *
381 * @since 1.0.0
382 *
383 * @access private
384 *
385 * @param WP_Post|null $chart The chart object.
386 *
387 * @return array The array of chart data.
388 */
389 private function _getChartArray( $chart = null ) {
390 if ( is_null( $chart ) ) {
391 $chart = $this->_chart;
392 }
393 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
394 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ), $chart->ID, $type );
395 $data = self::get_chart_data( $chart, $type );
396 $library = $this->load_chart_type( $chart->ID );
397
398 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
399 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
400 if ( ! empty( $atts['settings'] ) ) {
401 $settings = apply_filters( $atts['settings'], $settings, $chart->ID, $type );
402 }
403
404 $css = '';
405 $arguments = $this->get_inline_custom_css( 'visualizer-' . $chart->ID, $settings );
406 if ( ! empty( $arguments ) ) {
407 $css = $arguments[0];
408 $settings = $arguments[1];
409 }
410
411 $date_formats = Visualizer_Source::get_date_formats_if_exists( $series, $data );
412
413 $code = '';
414 if ( 'd3' === $library ) {
415 $code = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
416 }
417
418 return array(
419 'type' => $type,
420 'series' => $series,
421 'settings' => $settings,
422 'data' => $data,
423 'library' => $library,
424 'code' => $code,
425 'css' => $css,
426 'date_formats' => $date_formats,
427 );
428 }
429
430 /**
431 * Sends json response.
432 *
433 * @since 1.0.0
434 *
435 * @access private
436 *
437 * @param array $results The response array.
438 */
439 public static function _sendResponse( $results ) {
440 header( 'Content-type: application/json' );
441 nocache_headers();
442 echo json_encode( $results );
443 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
444 }
445
446 /**
447 * Deletes a chart from database.
448 *
449 * @since 1.0.0
450 * @uses wp_delete_post() To delete a chart.
451 *
452 * @access public
453 */
454 public function deleteChart() {
455 $is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
456 $input_method = $is_post ? INPUT_POST : INPUT_GET;
457 $chart_id = $success = false;
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',
464 FILTER_VALIDATE_INT,
465 array(
466 'options' => array(
467 'min_range' => 1,
468 ),
469 )
470 );
471 if ( $chart_id ) {
472 $chart = get_post( $chart_id );
473 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
474 }
475 }
476 if ( $success ) {
477 global $sitepress;
478 if ( Visualizer_Module::is_pro() && ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) ) {
479 $trid = $sitepress->get_element_trid( $chart_id, 'post_' . Visualizer_Plugin::CPT_VISUALIZER );
480 $translations = $sitepress->get_element_translations( $trid );
481 if ( ! empty( $translations ) ) {
482 foreach ( $translations as $translated_post ) {
483 wp_delete_post( $translated_post->element_id, true );
484 }
485 } else {
486 wp_delete_post( $chart_id, true );
487 }
488 } else {
489 wp_delete_post( $chart_id, true );
490 }
491 }
492 if ( $is_post ) {
493 self::_sendResponse(
494 array(
495 'success' => $success,
496 )
497 );
498 }
499 wp_redirect( remove_query_arg( 'vaction', wp_get_referer() ) );
500 exit;
501 }
502
503 /**
504 * Delete charts that are still in auto-draft mode.
505 */
506 private function deleteOldCharts() {
507 $query = new WP_Query(
508 array(
509 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
510 'post_status' => 'auto-draft',
511 'fields' => 'ids',
512 'update_post_meta_cache' => false,
513 'update_post_term_cache' => false,
514 'posts_per_page' => 50,
515 'date_query' => array(
516 array(
517 'before' => 'today',
518 ),
519 ),
520 )
521 );
522
523 if ( $query->have_posts() ) {
524 $ids = array();
525 while ( $query->have_posts() ) {
526 wp_delete_post( $query->next_post(), true );
527 }
528 }
529 }
530
531 /**
532 * Renders appropriate page for chart builder. Creates new auto draft chart
533 * if no chart has been specified.
534 *
535 * @since 1.0.0
536 *
537 * @access public
538 */
539 public function renderChartPages() {
540 if ( ! current_user_can( 'edit_posts' ) ) {
541 wp_die( __( 'You do not have permission to access this page.', 'visualizer' ) );
542 }
543
544 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
545 if ( ! defined( 'ET_BUILDER_PRODUCT_VERSION' ) && function_exists( 'et_get_theme_version' ) ) {
546 define( 'ET_BUILDER_PRODUCT_VERSION', et_get_theme_version() );
547 }
548 // Set current screen for the render chart.
549 set_current_screen( 'visualizer_render_chart' );
550 // check chart, if chart not exists, will create new one and redirects to the same page with proper chart id
551 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
552 if ( ! empty( $_POST ) ) {
553 $_POST = map_deep( $_POST, 'wp_strip_all_tags' );
554 }
555 $chart = $chart_id ? get_post( $chart_id ) : null;
556 if ( ! $chart_id || ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
557 if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
558 $this->deleteOldCharts();
559 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
560 $chart_status = Visualizer_Module_Admin::checkChartStatus( $default_type );
561 if ( ! $chart_status ) {
562 $default_type = 'line';
563 }
564 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $default_type . '.csv' );
565 $source->fetch();
566 $chart_id = wp_insert_post(
567 array(
568 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
569 'post_title' => 'Visualization',
570 'post_author' => get_current_user_id(),
571 'post_status' => 'auto-draft',
572 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
573 )
574 );
575 if ( $chart_id && ! is_wp_error( $chart_id ) ) {
576 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, $default_type );
577 add_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 1 );
578 add_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
579 add_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
580 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, '' );
581 add_post_meta(
582 $chart_id,
583 Visualizer_Plugin::CF_SETTINGS,
584 array(
585 'focusTarget' => 'datum',
586 )
587 );
588
589 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
590 }
591 } else {
592 $parent_chart_id = filter_var( $_GET['parent_chart_id'], FILTER_VALIDATE_INT );
593 $success = false;
594 if ( $parent_chart_id ) {
595 $parent_chart = get_post( $parent_chart_id );
596 $success = $parent_chart && $parent_chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
597 }
598 if ( $success ) {
599 $new_chart_id = wp_insert_post(
600 array(
601 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
602 'post_title' => 'Visualization',
603 'post_author' => get_current_user_id(),
604 'post_status' => $parent_chart->post_status,
605 'post_content' => $parent_chart->post_content,
606 )
607 );
608
609 if ( is_wp_error( $new_chart_id ) ) {
610 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Error while cloning chart %d = %s', $parent_chart_id, print_r( $new_chart_id, true ) ), 'error', __FILE__, __LINE__ );
611 } else {
612 $post_meta = get_post_meta( $parent_chart_id );
613 $chart_id = $new_chart_id;
614 foreach ( $post_meta as $key => $value ) {
615 if ( strpos( $key, 'visualizer-' ) !== false ) {
616 add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
617 }
618 }
619 }
620 }
621 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
622 }
623 wp_redirect( esc_url_raw( add_query_arg( 'chart', (int) $chart_id ) ) );
624
625 if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
626 wp_die();
627 }
628 exit();
629 }
630
631 $_POST['save_chart_image'] = isset( $_POST['save_chart_image'] ) && 'yes' === $_POST['save_chart_image'] ? true : false;
632 $_POST['lazy_load_chart'] = isset( $_POST['lazy_load_chart'] ) && 'yes' === $_POST['lazy_load_chart'] ? true : false;
633
634 if ( isset( $_POST['chart-img'] ) && ! empty( $_POST['chart-img'] ) ) {
635 $attachment_id = $this->save_chart_image( $_POST['chart-img'], $chart_id, $_POST['save_chart_image'] );
636 if ( $attachment_id ) {
637 update_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, $attachment_id );
638 }
639 }
640 $lib = $this->load_chart_type( $chart_id );
641
642 // the alpha color picker (RGBA) is not supported by google.
643 $color_picker_dep = 'wp-color-picker';
644 if ( in_array( $lib, array( 'chartjs', 'datatables' ), true ) && ! wp_script_is( 'wp-color-picker-alpha', 'registered' ) ) {
645 wp_register_script( 'wp-color-picker-alpha', VISUALIZER_ABSURL . 'js/lib/wp-color-picker-alpha.min.js', array( 'wp-color-picker' ), Visualizer_Plugin::VERSION );
646 $color_picker_dep = 'wp-color-picker-alpha';
647 }
648
649 // enqueue and register scripts and styles
650 wp_register_script( 'visualizer-chosen', VISUALIZER_ABSURL . 'js/lib/chosen.jquery.min.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
651 wp_register_style( 'visualizer-chosen', VISUALIZER_ABSURL . 'css/lib/chosen.min.css', array(), Visualizer_Plugin::VERSION );
652
653 wp_register_style( 'visualizer-frame', VISUALIZER_ABSURL . 'css/frame.css', array( 'visualizer-chosen' ), Visualizer_Plugin::VERSION );
654 wp_register_script( 'visualizer-frame', VISUALIZER_ABSURL . 'js/frame.js', array( 'visualizer-chosen', 'jquery-ui-accordion', 'jquery-ui-tabs' ), Visualizer_Plugin::VERSION, true );
655 wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
656 wp_register_script(
657 'visualizer-render',
658 VISUALIZER_ABSURL . 'js/render-facade.js',
659 apply_filters( 'visualizer_assets_render', array( 'visualizer-frame', 'visualizer-customization' ), false ),
660 Visualizer_Plugin::VERSION,
661 true
662 );
663 wp_register_script(
664 'visualizer-preview',
665 VISUALIZER_ABSURL . 'js/preview.js',
666 array(
667 $color_picker_dep,
668 'visualizer-render',
669 ),
670 Visualizer_Plugin::VERSION,
671 true
672 );
673 wp_register_script( 'visualizer-editor-simple', VISUALIZER_ABSURL . 'js/simple-editor.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
674
675 do_action( 'visualizer_add_scripts' );
676
677 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
678 global $Visualizer_Pro;
679 $Visualizer_Pro->_addScriptsAndStyles();
680 }
681
682 // dispatch pages
683 $this->_chart = get_post( $chart_id );
684 $tab = isset( $_GET['tab'] ) && ! empty( $_GET['tab'] ) ? $_GET['tab'] : 'visualizer';
685
686 // skip chart type pages only for existing charts.
687 if ( VISUALIZER_SKIP_CHART_TYPE_PAGE && 'auto-draft' !== $this->_chart->post_status && ( ! empty( $_GET['tab'] ) && 'visualizer' === $_GET['tab'] ) ) {
688 $tab = 'settings';
689 }
690
691 if ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) {
692 // if the cancel button is clicked.
693 $this->undoRevisions( $chart_id, true );
694 } elseif ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
695 $this->handlePermissions();
696 // if the save button is clicked.
697 $this->undoRevisions( $chart_id, false );
698 } else {
699 // if the edit button is clicked.
700 $this->_chart = $this->handleExistingRevisions( $chart_id, $this->_chart );
701 }
702
703 // Clear existing chart cache.
704 if ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
705 $cache_key = Visualizer_Plugin::CF_CHART_CACHE . '_' . $chart_id;
706 if ( get_transient( $cache_key ) ) {
707 delete_transient( $cache_key );
708 }
709 }
710
711 switch ( $tab ) {
712 case 'settings':
713 $this->_handleDataAndSettingsPage();
714 break;
715 case 'type': // fall through.
716 case 'visualizer': // fall through.
717 $this->_handleTypesPage();
718 break;
719 default:
720 // this should never happen.
721 break;
722 }
723 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
724 }
725
726 /**
727 * Load code editor assets.
728 */
729 private function loadCodeEditorAssets( $chart_id ) {
730 global $wp_version;
731
732 $wp_scripts = wp_scripts();
733
734 // data tables assets.
735 wp_register_script( 'visualizer-datatables', VISUALIZER_ABSURL . 'js/lib/datatables.min.js', array( 'jquery-ui-core' ), Visualizer_Plugin::VERSION );
736 wp_register_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION );
737 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 );
738 wp_enqueue_script( 'visualizer-datatables' );
739 wp_enqueue_style( 'visualizer-jquery-ui' );
740
741 if ( ! Visualizer_Module::is_pro() ) {
742 return;
743 }
744
745 $table_col_mapping = Visualizer_Source_Query_Params::get_all_db_tables_column_mapping( $chart_id );
746
747 if ( version_compare( $wp_version, '4.9.0', '<' ) ) {
748 // code mirror assets.
749 wp_register_script( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
750 wp_register_script( 'visualizer-codemirror-placeholder', '//codemirror.net/addon/display/placeholder.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
751 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
752 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
753 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
754 wp_register_script( 'visualizer-codemirror-sql-hint', '//codemirror.net/addon/hint/sql-hint.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
755 wp_register_script( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.js', array( 'visualizer-codemirror-sql', 'visualizer-codemirror-sql-hint', 'visualizer-codemirror-placeholder', 'visualizer-codemirror-matchbrackets', 'visualizer-codemirror-closebrackets' ), Visualizer_Plugin::VERSION );
756 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
757 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
758
759 wp_enqueue_script( 'visualizer-codemirror-hint' );
760 wp_enqueue_style( 'visualizer-codemirror-hint' );
761 } else {
762 wp_enqueue_code_editor(
763 array(
764 'type' => 'sql',
765 'codemirror' => array(
766 'autofocus' => true,
767 'lineWrapping' => true,
768 'dragDrop' => false,
769 'matchBrackets' => true,
770 'autoCloseBrackets' => true,
771 'extraKeys' => array( 'Shift-Space' => 'autocomplete' ),
772 'hintOptions' => array( 'tables' => $table_col_mapping ),
773 ),
774 )
775 );
776 }
777
778 return $table_col_mapping;
779 }
780
781 /**
782 * Handle permissions from the new conslidated settings sidebar.
783 */
784 private function handlePermissions() {
785 if ( ! Visualizer_Module::is_pro() ) {
786 return;
787 }
788
789 // we will not support old free and new pro.
790 // handling new free and old pro.
791 if ( has_action( 'visualizer_pro_handle_tab' ) ) {
792 do_action( 'visualizer_pro_handle_tab', 'permissions', $this->_chart );
793 } else {
794 do_action( 'visualizer_handle_permissions', $this->_chart );
795 }
796 }
797
798 /**
799 * Handle data and settings page
800 */
801 private function _handleDataAndSettingsPage() {
802 if ( isset( $_POST['map_api_key'] ) ) {
803 update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
804 }
805
806 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
807 $is_canceled = isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] );
808 $is_newly_created = $this->_chart->post_status === 'auto-draft';
809
810 if ( $is_newly_created && ! $is_canceled ) {
811 $this->_chart->post_status = 'publish';
812
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.
814 // we do not want any difference in data so disable revisions temporarily.
815 $this->disableRevisionsTemporarily();
816
817 wp_update_post( $this->_chart->to_array() );
818 }
819 // save meta data only when it is NOT being canceled.
820 if ( ! $is_canceled ) {
821 $post_settings = $_POST;
822 $existing = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
823 if ( isset( $existing['colors'] ) && is_array( $existing['colors'] ) && ! isset( $post_settings['colors'] ) ) {
824 $post_settings['colors'] = $existing['colors'];
825 }
826 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $post_settings );
827
828 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
829 // this will help in searching with the chart id.
830 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
831 $title = null;
832 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
833 $title = $settings['title'];
834 if ( is_array( $title ) ) {
835 $title = $settings['title']['text'];
836 }
837 }
838 if ( empty( $title ) ) {
839 $title = $this->_chart->ID;
840 }
841 $settings['internal_title'] = $title;
842 $settings_label = isset( $settings['pieResidueSliceLabel'] ) ? $settings['pieResidueSliceLabel'] : '';
843 if ( empty( $settings_label ) ) {
844 $settings['pieResidueSliceLabel'] = esc_html__( 'Other', 'visualizer' );
845 } else {
846 $settings['pieResidueSliceLabel'] = $settings_label;
847 }
848 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
849 }
850 $render = new Visualizer_Render_Page_Send();
851 $render->text = sprintf( '[visualizer id="%d"]', $this->_chart->ID );
852 wp_iframe( array( $render, 'render' ) );
853 return;
854 }
855 $data = $this->_getChartArray();
856 $sidebar = '';
857 $sidebar_class = $this->load_chart_class_name( $this->_chart->ID );
858 if ( class_exists( $sidebar_class, true ) ) {
859 $sidebar = new $sidebar_class( $data['settings'] );
860 $sidebar->__series = $data['series'];
861 $sidebar->__data = $data['data'];
862 } else {
863 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
864 if ( $sidebar !== '' ) {
865 $sidebar->__series = $data['series'];
866 $sidebar->__data = $data['data'];
867 }
868 }
869 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
870 wp_enqueue_style( 'wp-color-picker' );
871 wp_enqueue_style( 'visualizer-frame' );
872 wp_enqueue_script( 'visualizer-preview' );
873 wp_enqueue_script( 'visualizer-chosen' );
874 wp_enqueue_script( 'visualizer-render' );
875 wp_enqueue_code_editor( array( 'type' => 'application/json' ) );
876
877 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
878 wp_enqueue_script( 'visualizer-editor-simple' );
879 wp_localize_script(
880 'visualizer-editor-simple',
881 'visualizer1',
882 array(
883 'ajax' => array(
884 'url' => admin_url( 'admin-ajax.php' ),
885 'nonces' => array(),
886 'actions' => array(),
887 ),
888 )
889 );
890 }
891
892 $table_col_mapping = $this->loadCodeEditorAssets( $this->_chart->ID );
893
894 wp_localize_script(
895 'visualizer-render',
896 'visualizer',
897 array(
898 'l10n' => array(
899 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
900 'loading' => esc_html__( 'Loading...', 'visualizer' ),
901 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
902 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
903 'save_settings' => __( 'You have modified the chart\'s settings. To modify the source/data again, you must save this chart and reopen it for editing. If you continue without saving the chart, you may lose your changes.', 'visualizer' ),
904 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
905 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. Use the Manual Configuration option instead.', 'visualizer' ),
906 ),
907 'charts' => array(
908 'canvas' => $data,
909 'id' => $this->_chart->ID,
910 ),
911 'language' => $this->get_language(),
912 'map_api_key' => get_option( 'visualizer-map-api-key' ),
913 'ajax' => array(
914 'url' => admin_url( 'admin-ajax.php' ),
915 'nonces' => array(
916 'permissions' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA ),
917 'db_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION ),
918 'json_get_roots' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION ),
919 'json_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION ),
920 'json_set_schedule' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION ),
921 ),
922 'actions' => array(
923 'permissions' => Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA,
924 'db_get_data' => Visualizer_Plugin::ACTION_FETCH_DB_DATA,
925 'json_get_roots' => Visualizer_Plugin::ACTION_JSON_GET_ROOTS,
926 'json_get_data' => Visualizer_Plugin::ACTION_JSON_GET_DATA,
927 'json_set_schedule' => Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE,
928 ),
929 ),
930 'db_query' => array(
931 'tables' => $table_col_mapping,
932 ),
933 'is_pro' => Visualizer_Module::is_pro(),
934 'page_type' => 'chart',
935 'json_tag_separator' => Visualizer_Source_Json::TAG_SEPARATOR,
936 'json_tag_separator_view' => Visualizer_Source_Json::TAG_SEPARATOR_VIEW,
937 'is_front' => false,
938 'rest_base' => get_rest_url( null, 'wc/v3/reports/' ),
939 )
940 );
941
942 $render = new Visualizer_Render_Page_Data();
943 $render->chart = $this->_chart;
944 $render->type = $data['type'];
945 $render->custom_css = $data['css'];
946 $render->sidebar = $sidebar;
947 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
948 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
949 ? esc_html__( 'Save Chart', 'visualizer' )
950 : esc_html__( 'Create Chart', 'visualizer' );
951
952 $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
953 } else {
954 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
955 }
956
957 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
958
959 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
960 global $Visualizer_Pro;
961 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
962 }
963
964 $this->_addAction( 'admin_head', 'renderFlattrScript' );
965 wp_iframe( array( $render, 'render' ) );
966 }
967
968 /**
969 * Handles chart type selection page.
970 *
971 * @since 1.0.0
972 *
973 * @access private
974 */
975 private function _handleTypesPage() {
976 // process post request
977 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ), 'visualizer-upload-data' ) ) {
978 $type = filter_input( INPUT_POST, 'type' );
979 $library = filter_input( INPUT_POST, 'chart-library' );
980 if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
981 if ( empty( $library ) ) {
982 // library cannot be empty.
983 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Chart library empty while creating the chart! Aborting...', 'error', __FILE__, __LINE__ );
984 return;
985 }
986
987 // save new chart type
988 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, $type );
989 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, $library );
990 // if the chart has default data, update it with appropriate default data for new type
991 if ( filter_var( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, true ), FILTER_VALIDATE_BOOLEAN ) ) {
992 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $type . '.csv' );
993 $source->fetch();
994 $this->_chart->post_content = $source->getData( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
995 wp_update_post( $this->_chart->to_array() );
996 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
997 }
998
999 Visualizer_Module_Utility::set_defaults( $this->_chart );
1000
1001 // redirect to next tab
1002 // changed by Ash/Upwork
1003 wp_redirect( esc_url_raw( add_query_arg( 'tab', 'settings' ) ) );
1004
1005 return;
1006 }
1007 }
1008 $render = new Visualizer_Render_Page_Types();
1009 $render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
1010 $render->types = Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' );
1011 $render->chart = $this->_chart;
1012 wp_enqueue_style( 'visualizer-frame' );
1013 wp_enqueue_script( 'visualizer-frame' );
1014 wp_iframe( array( $render, 'render' ) );
1015 }
1016
1017 /**
1018 * Renders flattr script in the iframe <head>
1019 *
1020 * @since 1.4.2
1021 * @action admin_head
1022 *
1023 * @access public
1024 */
1025 public function renderFlattrScript() {
1026 echo '';
1027 }
1028
1029 /**
1030 * Processes the CSV that is sent in the request as a string.
1031 *
1032 * @since 3.2.0
1033 */
1034 /**
1035 * Determines whether a remote URL serves an XLSX file.
1036 *
1037 * Used as a fallback when the URL path has no recognisable file extension
1038 * (e.g. SharePoint, signed S3 URLs, or "download?id=…" endpoints).
1039 *
1040 * Uses wp_safe_remote_get() to block requests to private/loopback addresses,
1041 * and streams the response to a temp file so no body data is held in memory
1042 * regardless of whether the server honours the Range header.
1043 *
1044 * The check relies on the ZIP magic number (PK\x03\x04) that every XLSX
1045 * file begins with, making it immune to misleading Content-Type headers
1046 * such as application/octet-stream. Content-Type is used as a last-resort
1047 * fallback only when the temp file is empty (e.g. a HEAD-only server).
1048 *
1049 * @access private
1050 * @param string $url The remote URL to probe.
1051 * @return bool TRUE if the file appears to be XLSX, FALSE otherwise.
1052 */
1053 private static function _url_is_xlsx( $url ) {
1054 $tmpfile = wp_tempnam( 'visualizer_xlsx_probe' );
1055 if ( ! $tmpfile ) {
1056 return false;
1057 }
1058
1059 $response = wp_safe_remote_get(
1060 $url,
1061 array(
1062 'timeout' => 10,
1063 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ),
1064 'headers' => array( 'Range' => 'bytes=0-3' ),
1065 'stream' => true,
1066 'filename' => $tmpfile,
1067 )
1068 );
1069
1070 if ( is_wp_error( $response ) ) {
1071 @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1072 return false;
1073 }
1074
1075 $magic = '';
1076 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
1077 $fh = @fopen( $tmpfile, 'rb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1078 if ( $fh ) {
1079 $magic = fread( $fh, 4 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
1080 fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1081 }
1082 @unlink( $tmpfile ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
1083
1084 if ( strlen( $magic ) >= 4 ) {
1085 // XLSX (and all ZIP-based Office formats) start with PK\x03\x04.
1086 return $magic === "PK\x03\x04";
1087 }
1088
1089 // Last resort: server returned an empty body (e.g. ignored Range and
1090 // returned only headers). Check Content-Type from the same response.
1091 // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
1092 return false !== strpos(
1093 wp_remote_retrieve_header( $response, 'content-type' ),
1094 'spreadsheetml'
1095 );
1096 }
1097
1098 /**
1099 * Parses a raw CSV string or editor payload and returns a source object.
1100 *
1101 * @access private
1102 * @param string $data The raw CSV data string.
1103 * @param string $editor_type The editor type ('text' or 'tabular').
1104 * @return Visualizer_Source|null The populated source object, or null on failure.
1105 */
1106 private function handleCSVasString( $data, $editor_type ) {
1107 $source = null;
1108
1109 switch ( $editor_type ) {
1110 case 'text':
1111 // data coming in from the text editor.
1112 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1113 $handle = fopen( $tmpfile, 'w' );
1114 $values = preg_split( '/[\n\r]+/', stripslashes( trim( $data ) ) );
1115 if ( $values ) {
1116 foreach ( $values as $row ) {
1117 if ( empty( $row ) ) {
1118 continue;
1119 }
1120 $row = explode( ',', $row );
1121 $row = array_map(
1122 function ( $r ) {
1123 return '' === $r ? ' ' : $r;
1124 },
1125 $row
1126 );
1127 $row = implode( ',', $row );
1128 // don't use fpucsv here because we need to just dump the data
1129 // minus the empty rows
1130 // because fputcsv needs to tokenize
1131 // we can standardize the CSV enclosure here and replace all ' with "
1132 // we can assume that if there are an even number of ' they should be changed to "
1133 // but that will screw up words like fo'c'sle
1134 // so here let's just assume ' will NOT be used for enclosures
1135 fwrite( $handle, $row );
1136 fwrite( $handle, PHP_EOL );
1137 }
1138 }
1139 $source = new Visualizer_Source_Csv( $tmpfile );
1140 fclose( $handle );
1141 break;
1142 case 'table':
1143 // Import from Chart
1144 // fall-through.
1145 case 'excel':
1146 // data coming in from the excel editor.
1147 $source = apply_filters( 'visualizer_pro_handle_chart_data', $data, '' );
1148 break;
1149 }
1150 return $source;
1151 }
1152
1153 /**
1154 * Parses the data uploaded as an HTML table.
1155 *
1156 * @since 3.2.0
1157 *
1158 * @access private
1159 */
1160 private function handleTabularData() {
1161 $csv = array();
1162 // the datatable mentions the headers twice, so lets remove the duplicates.
1163 $headers = array_unique( array_filter( $_POST['header'] ) );
1164 $types = $_POST['type'];
1165
1166 // capture all the indexes that correspond to excluded columns.
1167 $exclude = array();
1168 $index = 0;
1169 foreach ( $types as $type ) {
1170 if ( empty( $type ) ) {
1171 $exclude[] = $index;
1172 }
1173 ++$index;
1174 }
1175
1176 // when N headers are being renamed, the number of headers increases by N
1177 // because of the way datatable duplicates header information
1178 // so unset the headers that have been renamed.
1179 if ( count( $headers ) !== count( $types ) ) {
1180 $to = count( $headers );
1181 for ( $i = count( $types ); $i < $to; $i++ ) {
1182 unset( $headers[ $i + 1 ] );
1183 }
1184 }
1185
1186 $columns = array();
1187 for ( $i = 0; $i < count( $headers ); $i++ ) {
1188 if ( ! isset( $_POST[ 'data' . $i ] ) ) {
1189 continue;
1190 }
1191 $columns[ $i ] = $_POST[ 'data' . $i ];
1192 }
1193
1194 $csv[] = $headers;
1195 $csv[] = $types;
1196 for ( $j = 0; $j < count( $columns[0] ); $j++ ) {
1197 $row = array();
1198 for ( $i = 0; $i < count( $headers ); $i++ ) {
1199 $row[] = sanitize_text_field( $columns[ $i ][ $j ] );
1200 }
1201 $csv[] = $row;
1202 }
1203
1204 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1205 $handle = fopen( $tmpfile, 'w' );
1206
1207 if ( $csv ) {
1208 $index = 0;
1209 foreach ( $csv as $row ) {
1210 // remove all the cells corresponding to the excluded headers.
1211 foreach ( $exclude as $j ) {
1212 unset( $row[ $j ] );
1213 }
1214 fputcsv( $handle, $row );
1215 }
1216 }
1217 $source = new Visualizer_Source_Csv( $tmpfile );
1218 fclose( $handle );
1219 return $source;
1220 }
1221
1222 /**
1223 * Parses uploaded CSV file and saves new data for the chart.
1224 *
1225 * @since 1.0.0
1226 *
1227 * @access public
1228 */
1229 public function uploadData() {
1230 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1231 // otherwise, assume this is a normal web request.
1232 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1233
1234 // validate nonce
1235 if (
1236 ! isset( $_GET['nonce'] ) ||
1237 ! wp_verify_nonce( $_GET['nonce'], 'visualizer-upload-data' ) ||
1238 ! current_user_can( 'edit_posts' )
1239 ) {
1240 if ( ! $can_die ) {
1241 return;
1242 }
1243 status_header( 403 );
1244 exit;
1245 }
1246
1247 // check chart, if chart exists
1248 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1249 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1250 $chart = $chart_id ? get_post( $chart_id ) : null;
1251 if (
1252 ! $chart_id ||
1253 ! $chart ||
1254 $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ||
1255 ! current_user_can( 'edit_post', $chart_id )
1256 ) {
1257 if ( ! $can_die ) {
1258 return;
1259 }
1260 status_header( 400 );
1261 exit;
1262 }
1263
1264 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__ );
1265
1266 if ( ! isset( $_POST['vz-import-time'] ) ) {
1267 apply_filters( 'visualizer_pro_remove_schedule', $chart_id );
1268 }
1269
1270 if ( ! isset( $_POST['chart_data_src'] ) || Visualizer_Plugin::CF_SOURCE_FILTER !== $_POST['chart_data_src'] ) {
1271 // delete the filters in case this chart is being uploaded from other data sources
1272 delete_post_meta( $chart_id, Visualizer_Plugin::CF_FILTER_CONFIG );
1273 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_FILTER_CONFIG );
1274 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_DB_QUERY );
1275
1276 // delete "import from db" specific parameters.
1277 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
1278 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
1279 delete_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS );
1280 }
1281
1282 // delete json related data.
1283 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL );
1284 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT );
1285 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING );
1286 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_HEADERS );
1287
1288 // delete last error
1289 delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
1290
1291 // delete editor related data.
1292 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR );
1293
1294 // delete this so that a JSON import can be later edited manually without a problem.
1295 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE );
1296
1297 $source = null;
1298 $render = new Visualizer_Render_Page_Update();
1299
1300 $remote_data = false;
1301 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1302 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1303 }
1304 if ( false !== $remote_data ) {
1305 $remote_ext = strtolower( pathinfo( parse_url( $remote_data, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
1306 if ( 'xlsx' === $remote_ext || ( 'csv' !== $remote_ext && self::_url_is_xlsx( $remote_data ) ) ) {
1307 $source = new Visualizer_Source_Xlsx_Remote( $remote_data );
1308 } else {
1309 $source = new Visualizer_Source_Csv_Remote( $remote_data );
1310 }
1311 if ( isset( $_POST['vz-import-time'] ) ) {
1312 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1313 }
1314 } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] === 0 ) {
1315 $local_ext = strtolower( pathinfo( isset( $_FILES['local_data']['name'] ) ? $_FILES['local_data']['name'] : '', PATHINFO_EXTENSION ) );
1316 if ( 'xlsx' === $local_ext ) {
1317 $source = new Visualizer_Source_Xlsx( $_FILES['local_data']['tmp_name'] );
1318 } else {
1319 $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1320 }
1321 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1322 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1323 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1324 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
1325 $source = $this->handleTabularData();
1326 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1327 } else {
1328 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'CSV file with chart data was not uploaded for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1329 $render->message = esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' );
1330 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'No CSV file was received. Select a file and try uploading again.', 'visualizer' ) );
1331 }
1332
1333 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Uploaded data for chart %d with source %s', $chart_id, print_r( $source, true ) ), 'debug', __FILE__, __LINE__ );
1334
1335 if ( $source ) {
1336 if ( $source->fetch() ) {
1337 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1338 $populate = true;
1339 if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {
1340 $json = unserialize( $content );
1341 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1342 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1343 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
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__ );
1345 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Not populating chart data as source exists (%s) but data is empty!', $json['source'] ) );
1346 $populate = false;
1347 }
1348 }
1349 if ( $populate ) {
1350 $chart->post_content = $content;
1351 }
1352 wp_update_post( $chart->to_array() );
1353
1354 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1355 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1356 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1357
1358 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Updated post for chart %d', $chart_id ), 'debug', __FILE__, __LINE__ );
1359
1360 Visualizer_Module_Utility::set_defaults( $chart, null );
1361
1362 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1363 if ( isset( $settings['series'] ) && ! ( count( $settings['series'] ) - count( $source->getSeries() ) > 1 ) ) {
1364 $diff_total_series = abs( count( $settings['series'] ) - count( $source->getSeries() ) );
1365 if ( $diff_total_series ) {
1366 foreach ( range( 1, $diff_total_series ) as $k => $diff_series ) {
1367 $settings['series'][] = end( $settings['series'] );
1368 }
1369 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
1370 }
1371 }
1372
1373 $render->id = $chart->ID;
1374 $render->data = json_encode( $source->getRawData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1375 $render->series = json_encode( $source->getSeries() );
1376 $render->settings = json_encode( $settings );
1377 } else {
1378 $error = $source->get_error();
1379 if ( empty( $error ) ) {
1380 $error = esc_html__( 'The CSV file couldn\'t be read. Check that it\'s properly formatted and try again.', 'visualizer' );
1381 }
1382 $render->message = $error;
1383 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1384 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
1385 }
1386 } else {
1387 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unknown internal error for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1388 }
1389 $render->render();
1390 if ( ! $can_die ) {
1391 return;
1392 }
1393 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1394 }
1395
1396 /**
1397 * Clones the chart.
1398 *
1399 * @since 1.0.0
1400 *
1401 * @access public
1402 */
1403 public function cloneChart() {
1404 $chart_id = $success = false;
1405 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1406 $capable = current_user_can( 'edit_posts' );
1407 if ( $nonce && $capable ) {
1408 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1409 if ( $chart_id ) {
1410 $chart = get_post( $chart_id );
1411 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1412 }
1413 }
1414 $redirect = remove_query_arg( 'vaction', wp_get_referer() );
1415 if ( $success ) {
1416 $new_chart_id = wp_insert_post(
1417 array(
1418 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1419 'post_title' => 'Visualization',
1420 'post_author' => get_current_user_id(),
1421 'post_status' => $chart->post_status,
1422 'post_content' => $chart->post_content,
1423 )
1424 );
1425 if ( is_wp_error( $new_chart_id ) ) {
1426 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__ );
1427 } else {
1428 $post_meta = get_post_meta( $chart_id );
1429 foreach ( $post_meta as $key => $value ) {
1430 if ( strpos( $key, 'visualizer-' ) !== false ) {
1431 add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
1432 }
1433 }
1434 $redirect = esc_url(
1435 add_query_arg(
1436 array(
1437 'page' => 'visualizer',
1438 'type' => filter_input( INPUT_GET, 'type' ),
1439 'vaction' => false,
1440 ),
1441 admin_url( 'admin.php' )
1442 ),
1443 null,
1444 'db'
1445 );
1446 }
1447 }
1448
1449 if ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) {
1450 wp_die();
1451 }
1452 wp_redirect( $redirect );
1453 exit;
1454 }
1455
1456 /**
1457 * Exports the chart data
1458 *
1459 * @since 1.0.0
1460 *
1461 * @access public
1462 */
1463 public function exportData() {
1464 check_ajax_referer( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION, 'security' );
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 }
1481 }
1482 }
1483
1484 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1485 }
1486
1487 /**
1488 * Handles chart data page.
1489 *
1490 * @since 1.0.0
1491 *
1492 * @access private
1493 */
1494 private function _handleDataPage() {
1495 $data = $this->_getChartArray();
1496 $render = new Visualizer_Render_Page_Data();
1497 $render->chart = $this->_chart;
1498 $render->type = $data['type'];
1499
1500 if ( $data && $data['settings'] ) {
1501 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
1502 }
1503 wp_enqueue_style( 'visualizer-frame' );
1504 wp_enqueue_script( 'visualizer-render' );
1505 wp_localize_script(
1506 'visualizer-render',
1507 'visualizer',
1508 array(
1509 'l10n' => array(
1510 'invalid_source' => esc_html__( 'The URL you entered is invalid. Please enter a valid URL.', 'visualizer' ),
1511 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1512 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. To display percentages, use the Manual Configuration option instead.', 'visualizer' ),
1513 ),
1514 'charts' => array(
1515 'canvas' => $data,
1516 ),
1517 )
1518 );
1519
1520 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
1521
1522 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
1523 global $Visualizer_Pro;
1524 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
1525 }
1526
1527 // Added by Ash/Upwork
1528 $this->_addAction( 'admin_head', 'renderFlattrScript' );
1529 wp_iframe( array( $render, 'render' ) );
1530 }
1531
1532 /**
1533 * Returns the data for the query.
1534 *
1535 * @access public
1536 */
1537 public function getQueryData() {
1538 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1539
1540 if ( ! current_user_can( 'administrator' ) ) {
1541 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1542 }
1543 if ( ! is_super_admin() ) {
1544 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1545 }
1546
1547 if ( ! Visualizer_Module::is_pro() ) {
1548 wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1549 }
1550
1551 $params = wp_parse_args( $_POST['params'] );
1552 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1553 $query = trim( $params['query'], ';' );
1554
1555 $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1556 $html = $source->fetch( true );
1557 $error = $source->get_error();
1558 if ( ! empty( $error ) ) {
1559 wp_send_json_error( array( 'msg' => $error ) );
1560 }
1561 wp_send_json_success( array( 'table' => $html ) );
1562 }
1563
1564 /**
1565 * Saves the query and the schedule.
1566 *
1567 * @access public
1568 */
1569 public function saveQuery() {
1570 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1571
1572 if ( ! current_user_can( 'administrator' ) ) {
1573 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1574 }
1575 if ( ! is_super_admin() ) {
1576 wp_send_json_error( array( 'msg' => __( 'Action not allowed for this user.', 'visualizer' ) ) );
1577 }
1578
1579 if ( ! Visualizer_Module::is_pro() ) {
1580 wp_send_json_error( array( 'msg' => __( 'Feature is not available.', 'visualizer' ) ) );
1581 }
1582
1583 $chart_id = filter_input(
1584 INPUT_GET,
1585 'chart',
1586 FILTER_VALIDATE_INT,
1587 array(
1588 'options' => array(
1589 'min_range' => 1,
1590 ),
1591 )
1592 );
1593
1594 $hours = filter_input(
1595 INPUT_POST,
1596 'refresh',
1597 FILTER_VALIDATE_FLOAT,
1598 array(
1599 'options' => array(
1600 'min_range' => -1,
1601 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1602 ),
1603 )
1604 );
1605
1606 if ( ! is_numeric( $hours ) ) {
1607 $hours = -1;
1608 }
1609
1610 $render = new Visualizer_Render_Page_Update();
1611 if ( $chart_id ) {
1612 $params = wp_parse_args( $_POST['params'] );
1613 $query = trim( $params['query'], ';' );
1614 $source = new Visualizer_Source_Query( stripslashes( $query ), $chart_id, $params );
1615 $source->fetch( false );
1616 $error = $source->get_error();
1617 if ( empty( $error ) ) {
1618 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $query ) );
1619 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1620 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1621 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1622 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1623 if ( isset( $params['db_type'] ) && $params['db_type'] !== Visualizer_Plugin::WP_DB_NAME ) {
1624 $remote_db_params = $params;
1625 unset( $remote_db_params['query'] );
1626 unset( $remote_db_params['chart_id'] );
1627 update_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS, $remote_db_params );
1628 }
1629
1630 $schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
1631 $schedules[ $chart_id ] = time() + $hours * HOUR_IN_SECONDS;
1632 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $schedules );
1633
1634 wp_update_post(
1635 array(
1636 'ID' => $chart_id,
1637 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
1638 )
1639 );
1640 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1641 $render->series = json_encode( $source->getSeries() );
1642 $render->id = $chart_id;
1643 } else {
1644 $render->message = $error;
1645 }
1646 }
1647 $render->render();
1648 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1649 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1650 }
1651 }
1652
1653
1654 /**
1655 * Saves the filter query and the schedule.
1656 *
1657 * @access public
1658 */
1659 public function saveFilter() {
1660 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1661
1662 $chart_id = filter_input(
1663 INPUT_GET,
1664 'chart',
1665 FILTER_VALIDATE_INT,
1666 array(
1667 'options' => array(
1668 'min_range' => 1,
1669 ),
1670 )
1671 );
1672
1673 $hours = filter_input(
1674 INPUT_POST,
1675 'refresh',
1676 FILTER_VALIDATE_FLOAT,
1677 array(
1678 'options' => array(
1679 'min_range' => -1,
1680 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1681 ),
1682 )
1683 );
1684
1685 if ( ! is_numeric( $hours ) ) {
1686 $hours = -1;
1687 }
1688
1689 do_action( 'visualizer_save_filter', $chart_id, $hours );
1690
1691 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1692 ( defined( 'WP_TESTS_DOMAIN' ) && function_exists( 'tests_add_filter' ) ) ? wp_die() : exit();
1693 }
1694 }
1695
1696 /**
1697 * Save chart image.
1698 *
1699 * @param string $base64_img Chart image.
1700 * @param int $chart_id Chart ID.
1701 * @param bool $save_attachment Save attachment.
1702 * @return attachment ID
1703 */
1704 public function save_chart_image( $base64_img, $chart_id, $save_attachment = true ) {
1705 // Delete old chart image.
1706 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
1707 if ( $old_attachment_id ) {
1708 wp_delete_attachment( $old_attachment_id, true );
1709 }
1710
1711 if ( ! $save_attachment ) {
1712 return 0;
1713 }
1714
1715 // Upload dir.
1716 $upload_dir = wp_upload_dir();
1717 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1718
1719 $img = str_replace( 'data:image/png;base64,', '', $base64_img );
1720 $img = str_replace( ' ', '+', $img );
1721 $decoded = base64_decode( $img );
1722 $filename = 'visualization-' . $chart_id . '.png';
1723 $file_type = 'image/png';
1724 $hashed_filename = $filename;
1725
1726 // Save the image in the uploads directory.
1727 require_once ABSPATH . '/wp-admin/includes/file.php';
1728 \WP_Filesystem();
1729 global $wp_filesystem;
1730 if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
1731 $creds = request_filesystem_credentials( site_url() );
1732 wp_filesystem( $creds );
1733 }
1734 $upload_file = $wp_filesystem->put_contents( $upload_path . $hashed_filename, $decoded );
1735
1736 // Insert new chart image.
1737 $attachment = array(
1738 'post_mime_type' => $file_type,
1739 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $hashed_filename ) ),
1740 'post_content' => '',
1741 'post_status' => 'inherit',
1742 'guid' => $upload_dir['url'] . '/' . basename( $hashed_filename ),
1743 );
1744
1745 $attach_id = wp_insert_attachment( $attachment, $upload_dir['path'] . '/' . $hashed_filename );
1746 return $attach_id;
1747 }
1748 }
1749