PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.2
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.2
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.2, at classes/Visualizer/Module/Chart.php

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