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

1,525 lines 53.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Eugene Manuilov <eugene@manuilov.org> |
20 // +----------------------------------------------------------------------+
21 /**
22 * The module for all stuff related to getting, editing, creating and deleting charts.
23 *
24 * @category Visualizer
25 * @package Module
26 *
27 * @since 1.0.0
28 */
29 class Visualizer_Module_Chart extends Visualizer_Module {
30
31 const NAME = __CLASS__;
32
33 /**
34 * The chart object.
35 *
36 * @since 1.0.0
37 *
38 * @access private
39 * @var WP_Post
40 */
41 private $_chart;
42
43 /**
44 * Constructor.
45 *
46 * @since 1.0.0
47 *
48 * @access public
49 *
50 * @param Visualizer_Plugin $plugin The instance of the plugin.
51 */
52 public function __construct( Visualizer_Plugin $plugin ) {
53 parent::__construct( $plugin );
54 $this->_addAjaxAction( Visualizer_Plugin::ACTION_GET_CHARTS, 'getCharts' );
55 $this->_addAjaxAction( Visualizer_Plugin::ACTION_DELETE_CHART, 'deleteChart' );
56 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CREATE_CHART, 'renderChartPages' );
57 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EDIT_CHART, 'renderChartPages' );
58 $this->_addAjaxAction( Visualizer_Plugin::ACTION_UPLOAD_DATA, 'uploadData' );
59 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CLONE_CHART, 'cloneChart' );
60 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EXPORT_DATA, 'exportData' );
61
62 $this->_addAjaxAction( Visualizer_Plugin::ACTION_FETCH_DB_DATA, 'getQueryData' );
63 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_DB_QUERY, 'saveQuery' );
64
65 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_GET_ROOTS, 'getJsonRoots' );
66 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_GET_DATA, 'getJsonData' );
67 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_SET_DATA, 'setJsonData' );
68 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE, 'setJsonSchedule' );
69
70 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY, 'saveFilter' );
71
72 $this->_addFilter( 'visualizer_get_sidebar', 'getSidebar', 10, 2 );
73
74 }
75
76 /**
77 * Generates the HTML of the sidebar for the chart.
78 *
79 * @since ?
80 *
81 * @access public
82 */
83 public function getSidebar( $sidebar, $chart_id ) {
84 $chart = get_post( $chart_id );
85 $data = $this->_getChartArray( $chart );
86 $sidebar = '';
87 $sidebar_class = $this->load_chart_class_name( $chart_id );
88 if ( class_exists( $sidebar_class, true ) ) {
89 $sidebar = new $sidebar_class( $data['settings'] );
90 $sidebar->__series = $data['series'];
91 $sidebar->__data = $data['data'];
92 } else {
93 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
94 if ( $sidebar !== '' ) {
95 $sidebar->__series = $data['series'];
96 $sidebar->__data = $data['data'];
97 }
98 }
99 return str_replace( "'", '"', $sidebar->__toString() );
100 }
101
102 /**
103 * Sets the schedule for how JSON-endpoint charts should be updated.
104 *
105 * @since ?
106 *
107 * @access public
108 */
109 public function setJsonSchedule() {
110 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION, 'security' );
111
112 $chart_id = filter_input(
113 INPUT_POST,
114 'chart',
115 FILTER_VALIDATE_INT,
116 array(
117 'options' => array(
118 'min_range' => 1,
119 ),
120 )
121 );
122
123 if ( ! $chart_id ) {
124 wp_send_json_error();
125 }
126
127 $time = filter_input(
128 INPUT_POST,
129 'time',
130 FILTER_VALIDATE_INT,
131 array(
132 'options' => array(
133 'min_range' => -1,
134 ),
135 )
136 );
137
138 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE );
139
140 if ( -1 < $time ) {
141 add_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, $time );
142 // Update schedules.
143 $schedules = get_option( Visualizer_Plugin::CF_JSON_SCHEDULE, array() );
144 $schedules[ $chart_id ] = time() + $time * HOUR_IN_SECONDS;
145 update_option( Visualizer_Plugin::CF_JSON_SCHEDULE, $schedules );
146 }
147 wp_send_json_success();
148 }
149
150 /**
151 * Get the root elements for JSON-endpoint.
152 *
153 * @since ?
154 *
155 * @access public
156 */
157 public function getJsonRoots() {
158 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION, 'security' );
159
160 $params = wp_parse_args( $_POST['params'] );
161
162 $source = new Visualizer_Source_Json( $params );
163
164 $roots = $source->fetchRoots();
165 if ( empty( $roots ) ) {
166 wp_send_json_error( array( 'msg' => $source->get_error() ) );
167 }
168
169 wp_send_json_success( array( 'url' => $params['url'], 'roots' => $roots ) );
170 }
171
172 /**
173 * Get the data for the JSON-endpoint corresponding to the chosen root.
174 *
175 * @since ?
176 *
177 * @access public
178 */
179 public function getJsonData() {
180 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION, 'security' );
181
182 $params = wp_parse_args( $_POST['params'] );
183
184 $chart_id = $params['chart'];
185
186 if ( empty( $chart_id ) ) {
187 wp_die();
188 }
189
190 $source = new Visualizer_Source_Json( $params );
191 $source->fetch();
192 $data = $source->getRawData();
193
194 if ( empty( $data ) ) {
195 wp_send_json_error( array( 'msg' => esc_html__( 'Unable to fetch data from the endpoint. Please try again.', 'visualizer' ) ) );
196 }
197
198 $data = Visualizer_Render_Layout::show( 'editor-table', $data, $chart_id, 'viz-json-table', false, false );
199 wp_send_json_success( array( 'table' => $data, 'root' => $params['root'], 'url' => $params['url'], 'paging' => $source->getPaginationElements() ) );
200 }
201
202 /**
203 * Updates the database with the correct post parameters for JSON-endpoint charts.
204 *
205 * @since ?
206 *
207 * @access public
208 */
209 public function setJsonData() {
210 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_DATA . Visualizer_Plugin::VERSION, 'security' );
211
212 $params = $_POST;
213 $chart_id = $_GET['chart'];
214
215 if ( empty( $chart_id ) ) {
216 wp_die();
217 }
218
219 $chart = get_post( $chart_id );
220
221 $source = new Visualizer_Source_Json( $params );
222 update_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true );
223 $source->fetchFromEditableTable();
224
225 $content = $source->getData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
226 $chart->post_content = $content;
227 wp_update_post( $chart->to_array() );
228 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
229 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
230 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
231 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_URL, $params['url'] );
232 update_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_ROOT, $params['root'] );
233
234 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_HEADERS );
235 $headers = array( 'method' => $params['method'] );
236 if ( ! empty( $params['auth'] ) ) {
237 $headers['auth'] = $params['auth'];
238 } elseif ( ! empty( $params['username'] ) && ! empty( $params['password'] ) ) {
239 $headers['auth'] = array( 'username' => $params['username'], 'password' => $params['password'] );
240 }
241
242 add_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_HEADERS, $headers );
243
244 delete_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_PAGING );
245 if ( ! empty( $params['paging'] ) ) {
246 add_post_meta( $chart->ID, Visualizer_Plugin::CF_JSON_PAGING, $params['paging'] );
247 }
248
249 $time = filter_input(
250 INPUT_POST,
251 'time',
252 FILTER_VALIDATE_INT,
253 array(
254 'options' => array(
255 'min_range' => -1,
256 ),
257 )
258 );
259
260 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE );
261
262 if ( -1 < $time ) {
263 add_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, $time );
264 }
265
266 // delete other source specific parameters.
267 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
268 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
269 delete_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_URL );
270 delete_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_SCHEDULE );
271
272 $render = new Visualizer_Render_Page_Update();
273 $render->id = $chart->ID;
274 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
275 $render->series = json_encode( $source->getSeries() );
276 $render->render();
277
278 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
279 }
280
281
282 /**
283 * Fetches charts from database.
284 *
285 * This method is also called from the media pop-up (classic editor: create a post and add chart from insert content).
286 *
287 * @since 1.0.0
288 *
289 * @access public
290 */
291 public function getCharts() {
292 $query_args = array(
293 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
294 'posts_per_page' => 9,
295 'paged' => filter_input(
296 INPUT_GET,
297 'page',
298 FILTER_VALIDATE_INT,
299 array(
300 'options' => array(
301 'min_range' => 1,
302 'default' => 1,
303 ),
304 )
305 ),
306 );
307 $filter = filter_input( INPUT_GET, 's', FILTER_SANITIZE_STRING );
308 if ( empty( $filter ) ) {
309 // 'filter' is from the modal from the add media button.
310 $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
311 }
312
313 if ( $filter && in_array( $filter, Visualizer_Plugin::getChartTypes(), true ) ) {
314 $query_args['meta_query'] = array(
315 array(
316 'key' => Visualizer_Plugin::CF_CHART_TYPE,
317 'value' => $filter,
318 'compare' => '=',
319 ),
320 );
321 }
322 $query = new WP_Query( $query_args );
323 $charts = array();
324 while ( $query->have_posts() ) {
325 $chart = $query->next_post();
326 $chart_data = $this->_getChartArray( $chart );
327 $chart_data['id'] = $chart->ID;
328 $chart_data['library'] = $this->load_chart_type( $chart->ID );
329 $css = '';
330 $settings = $chart_data['settings'];
331 $arguments = $this->get_inline_custom_css( 'visualizer-chart-' . $chart->ID, $settings );
332 if ( ! empty( $arguments ) ) {
333 $css = $arguments[0];
334 $settings = $arguments[1];
335 }
336 $chart_data['settings'] = $settings;
337 $chart_data['css'] = $css;
338 $charts[] = $chart_data;
339 }
340 self::_sendResponse(
341 array(
342 'success' => true,
343 'data' => $charts,
344 'total' => $query->max_num_pages,
345 )
346 );
347 }
348
349 /**
350 * Returns chart data required for rendering.
351 *
352 * @since 1.0.0
353 *
354 * @access private
355 *
356 * @param WP_Post $chart The chart object.
357 *
358 * @return array The array of chart data.
359 */
360 private function _getChartArray( WP_Post $chart = null ) {
361 if ( is_null( $chart ) ) {
362 $chart = $this->_chart;
363 }
364 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
365 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ), $chart->ID, $type );
366 $data = self::get_chart_data( $chart, $type );
367 $library = $this->load_chart_type( $chart->ID );
368
369 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
370 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
371 if ( ! empty( $atts['settings'] ) ) {
372 $settings = apply_filters( $atts['settings'], $settings, $chart->ID, $type );
373 }
374
375 $css = '';
376 $arguments = $this->get_inline_custom_css( 'visualizer-' . $chart->ID, $settings );
377 if ( ! empty( $arguments ) ) {
378 $css = $arguments[0];
379 $settings = $arguments[1];
380 }
381
382 $date_formats = Visualizer_Source::get_date_formats_if_exists( $series, $data );
383
384 return array(
385 'type' => $type,
386 'series' => $series,
387 'settings' => $settings,
388 'data' => $data,
389 'library' => $library,
390 'css' => $css,
391 'date_formats' => $date_formats,
392 );
393 }
394
395 /**
396 * Sends json response.
397 *
398 * @since 1.0.0
399 *
400 * @access private
401 *
402 * @param array $results The response array.
403 */
404 public static function _sendResponse( $results ) {
405 header( 'Content-type: application/json' );
406 nocache_headers();
407 echo json_encode( $results );
408 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
409 }
410
411 /**
412 * Deletes a chart from database.
413 *
414 * @since 1.0.0
415 * @uses wp_delete_post() To delete a chart.
416 *
417 * @access public
418 */
419 public function deleteChart() {
420 $is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
421 $input_method = $is_post ? INPUT_POST : INPUT_GET;
422 $chart_id = $success = false;
423 $nonce = wp_verify_nonce( filter_input( $input_method, 'nonce' ) );
424 $capable = current_user_can( 'delete_posts' );
425 if ( $nonce && $capable ) {
426 $chart_id = filter_input(
427 $input_method,
428 'chart',
429 FILTER_VALIDATE_INT,
430 array(
431 'options' => array(
432 'min_range' => 1,
433 ),
434 )
435 );
436 if ( $chart_id ) {
437 $chart = get_post( $chart_id );
438 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
439 }
440 }
441 if ( $success ) {
442 wp_delete_post( $chart_id, true );
443 }
444 if ( $is_post ) {
445 self::_sendResponse(
446 array(
447 'success' => $success,
448 )
449 );
450 }
451 wp_redirect( remove_query_arg( 'vaction', wp_get_referer() ) );
452 exit;
453 }
454
455 /**
456 * Delete charts that are still in auto-draft mode.
457 */
458 private function deleteOldCharts() {
459 $query = new WP_Query(
460 array(
461 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
462 'post_status' => 'auto-draft',
463 'fields' => 'ids',
464 'update_post_meta_cache' => false,
465 'update_post_term_cache' => false,
466 'posts_per_page' => 50,
467 'date_query' => array(
468 array(
469 'before' => 'today',
470 ),
471 ),
472 )
473 );
474
475 if ( $query->have_posts() ) {
476 $ids = array();
477 while ( $query->have_posts() ) {
478 wp_delete_post( $query->next_post(), true );
479 }
480 }
481 }
482
483 /**
484 * Renders appropriate page for chart builder. Creates new auto draft chart
485 * if no chart has been specified.
486 *
487 * @since 1.0.0
488 *
489 * @access public
490 */
491 public function renderChartPages() {
492 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
493 if ( ! defined( 'ET_BUILDER_PRODUCT_VERSION' ) && function_exists( 'et_get_theme_version' ) ) {
494 define( 'ET_BUILDER_PRODUCT_VERSION', et_get_theme_version() );
495 }
496 // Set current screen for the render chart.
497 set_current_screen( 'visualizer_render_chart' );
498 // check chart, if chart not exists, will create new one and redirects to the same page with proper chart id
499 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
500 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
501 $this->deleteOldCharts();
502 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
503 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $default_type . '.csv' );
504 $source->fetch();
505 $chart_id = wp_insert_post(
506 array(
507 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
508 'post_title' => 'Visualization',
509 'post_author' => get_current_user_id(),
510 'post_status' => 'auto-draft',
511 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
512 )
513 );
514 if ( $chart_id && ! is_wp_error( $chart_id ) ) {
515 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, $default_type );
516 add_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 1 );
517 add_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
518 add_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
519 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, '' );
520 add_post_meta(
521 $chart_id,
522 Visualizer_Plugin::CF_SETTINGS,
523 array(
524 'focusTarget' => 'datum',
525 )
526 );
527 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
528 }
529 wp_redirect( esc_url_raw( add_query_arg( 'chart', (int) $chart_id ) ) );
530
531 if ( defined( 'WP_TESTS_DOMAIN' ) ) {
532 wp_die();
533 }
534 exit();
535 }
536
537 $_POST['save_chart_image'] = isset( $_POST['save_chart_image'] ) && 'yes' === $_POST['save_chart_image'] ? true : false;
538 $_POST['lazy_load_chart'] = isset( $_POST['lazy_load_chart'] ) && 'yes' === $_POST['lazy_load_chart'] ? true : false;
539
540 if ( isset( $_POST['chart-img'] ) && ! empty( $_POST['chart-img'] ) ) {
541 $attachment_id = $this->save_chart_image( $_POST['chart-img'], $chart_id, $_POST['save_chart_image'] );
542 if ( $attachment_id ) {
543 update_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, $attachment_id );
544 }
545 }
546 $lib = $this->load_chart_type( $chart_id );
547
548 // the alpha color picker (RGBA) is not supported by google.
549 $color_picker_dep = 'wp-color-picker';
550 if ( in_array( $lib, array( 'chartjs', 'datatables' ), true ) && ! wp_script_is( 'wp-color-picker-alpha', 'registered' ) ) {
551 wp_register_script( 'wp-color-picker-alpha', VISUALIZER_ABSURL . 'js/lib/wp-color-picker-alpha.min.js', array( 'wp-color-picker' ), Visualizer_Plugin::VERSION );
552 $color_picker_dep = 'wp-color-picker-alpha';
553 }
554
555 // enqueue and register scripts and styles
556 wp_register_script( 'visualizer-chosen', VISUALIZER_ABSURL . 'js/lib/chosen.jquery.min.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
557 wp_register_style( 'visualizer-chosen', VISUALIZER_ABSURL . 'css/lib/chosen.min.css', array(), Visualizer_Plugin::VERSION );
558
559 wp_register_style( 'visualizer-frame', VISUALIZER_ABSURL . 'css/frame.css', array( 'visualizer-chosen' ), Visualizer_Plugin::VERSION );
560 wp_register_script( 'visualizer-frame', VISUALIZER_ABSURL . 'js/frame.js', array( 'visualizer-chosen', 'jquery-ui-accordion', 'jquery-ui-tabs' ), Visualizer_Plugin::VERSION, true );
561 wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
562 wp_register_script(
563 'visualizer-render',
564 VISUALIZER_ABSURL . 'js/render-facade.js',
565 apply_filters( 'visualizer_assets_render', array( 'visualizer-frame', 'visualizer-customization' ), false ),
566 Visualizer_Plugin::VERSION,
567 true
568 );
569 wp_register_script(
570 'visualizer-preview',
571 VISUALIZER_ABSURL . 'js/preview.js',
572 array(
573 $color_picker_dep,
574 'visualizer-render',
575 ),
576 Visualizer_Plugin::VERSION,
577 true
578 );
579 wp_register_script( 'visualizer-editor-simple', VISUALIZER_ABSURL . 'js/simple-editor.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
580
581 do_action( 'visualizer_add_scripts' );
582
583 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
584 global $Visualizer_Pro;
585 $Visualizer_Pro->_addScriptsAndStyles();
586 }
587
588 // dispatch pages
589 $this->_chart = get_post( $chart_id );
590 $tab = isset( $_GET['tab'] ) && ! empty( $_GET['tab'] ) ? $_GET['tab'] : 'visualizer';
591
592 // skip chart type pages only for existing charts.
593 if ( VISUALIZER_SKIP_CHART_TYPE_PAGE && 'auto-draft' !== $this->_chart->post_status && ( ! empty( $_GET['tab'] ) && 'visualizer' === $_GET['tab'] ) ) {
594 $tab = 'settings';
595 }
596
597 if ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) {
598 // if the cancel button is clicked.
599 $this->undoRevisions( $chart_id, true );
600 } elseif ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
601 $this->handlePermissions();
602 // if the save button is clicked.
603 $this->undoRevisions( $chart_id, false );
604 } else {
605 // if the edit button is clicked.
606 $this->_chart = $this->handleExistingRevisions( $chart_id, $this->_chart );
607 }
608
609 // Clear existing chart cache.
610 if ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
611 $cache_key = Visualizer_Plugin::CF_CHART_CACHE . '_' . $chart_id;
612 if ( get_transient( $cache_key ) ) {
613 delete_transient( $cache_key );
614 }
615 }
616
617 switch ( $tab ) {
618 case 'settings':
619 $this->_handleDataAndSettingsPage();
620 break;
621 case 'type': // fall through.
622 case 'visualizer': // fall through.
623 $this->_handleTypesPage();
624 break;
625 default:
626 // this should never happen.
627 break;
628 }
629 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
630 }
631
632 /**
633 * Load code editor assets.
634 */
635 private function loadCodeEditorAssets( $chart_id ) {
636 global $wp_version;
637
638 $wp_scripts = wp_scripts();
639
640 // data tables assets.
641 wp_register_script( 'visualizer-datatables', VISUALIZER_ABSURL . 'js/lib/datatables.min.js', array( 'jquery-ui-core' ), Visualizer_Plugin::VERSION );
642 wp_register_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION );
643 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 );
644 wp_enqueue_script( 'visualizer-datatables' );
645 wp_enqueue_style( 'visualizer-jquery-ui' );
646
647 if ( ! Visualizer_Module::is_pro() ) {
648 return;
649 }
650
651 $table_col_mapping = Visualizer_Source_Query_Params::get_all_db_tables_column_mapping( $chart_id );
652
653 if ( version_compare( $wp_version, '4.9.0', '<' ) ) {
654 // code mirror assets.
655 wp_register_script( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
656 wp_register_script( 'visualizer-codemirror-placeholder', '//codemirror.net/addon/display/placeholder.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
657 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
658 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
659 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
660 wp_register_script( 'visualizer-codemirror-sql-hint', '//codemirror.net/addon/hint/sql-hint.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
661 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 );
662 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
663 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
664
665 wp_enqueue_script( 'visualizer-codemirror-hint' );
666 wp_enqueue_style( 'visualizer-codemirror-hint' );
667 } else {
668 wp_enqueue_code_editor(
669 array(
670 'type' => 'sql',
671 'codemirror' => array(
672 'autofocus' => true,
673 'lineWrapping' => true,
674 'dragDrop' => false,
675 'matchBrackets' => true,
676 'autoCloseBrackets' => true,
677 'extraKeys' => array( 'Ctrl-Space' => 'autocomplete' ),
678 'hintOptions' => array( 'tables' => $table_col_mapping ),
679 ),
680 )
681 );
682 }
683
684 return $table_col_mapping;
685 }
686
687 /**
688 * Handle permissions from the new conslidated settings sidebar.
689 */
690 private function handlePermissions() {
691 if ( ! Visualizer_Module::is_pro() ) {
692 return;
693 }
694
695 // we will not support old free and new pro.
696 // handling new free and old pro.
697 if ( has_action( 'visualizer_pro_handle_tab' ) ) {
698 do_action( 'visualizer_pro_handle_tab', 'permissions', $this->_chart );
699 } else {
700 do_action( 'visualizer_handle_permissions', $this->_chart );
701 }
702 }
703
704 /**
705 * Handle data and settings page
706 */
707 private function _handleDataAndSettingsPage() {
708 if ( isset( $_POST['map_api_key'] ) ) {
709 update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
710 }
711
712 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
713 if ( $this->_chart->post_status === 'auto-draft' ) {
714 $this->_chart->post_status = 'publish';
715
716 // 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.
717 // we do not want any difference in data so disable revisions temporarily.
718 $this->disableRevisionsTemporarily();
719
720 wp_update_post( $this->_chart->to_array() );
721 }
722 // save meta data only when it is NOT being canceled.
723 if ( ! ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) ) {
724 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $_POST );
725
726 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
727 // this will help in searching with the chart id.
728 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
729 $title = null;
730 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
731 $title = $settings['title'];
732 if ( is_array( $title ) ) {
733 $title = $settings['title']['text'];
734 }
735 }
736 if ( empty( $title ) ) {
737 $title = $this->_chart->ID;
738 }
739 $settings['internal_title'] = $title;
740 $settings_label = $settings['pieResidueSliceLabel'];
741 if ( empty( $settings_label ) ) {
742 $settings['pieResidueSliceLabel'] = esc_html__( 'Other', 'visualizer' );
743 } else {
744 $settings['pieResidueSliceLabel'] = $settings_label;
745 }
746 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
747 }
748 $render = new Visualizer_Render_Page_Send();
749 $render->text = sprintf( '[visualizer id="%d"]', $this->_chart->ID );
750 wp_iframe( array( $render, 'render' ) );
751 return;
752 }
753 $data = $this->_getChartArray();
754 $sidebar = '';
755 $sidebar_class = $this->load_chart_class_name( $this->_chart->ID );
756 if ( class_exists( $sidebar_class, true ) ) {
757 $sidebar = new $sidebar_class( $data['settings'] );
758 $sidebar->__series = $data['series'];
759 $sidebar->__data = $data['data'];
760 } else {
761 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
762 if ( $sidebar !== '' ) {
763 $sidebar->__series = $data['series'];
764 $sidebar->__data = $data['data'];
765 }
766 }
767 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
768 wp_enqueue_style( 'wp-color-picker' );
769 wp_enqueue_style( 'visualizer-frame' );
770 wp_enqueue_script( 'visualizer-preview' );
771 wp_enqueue_script( 'visualizer-chosen' );
772 wp_enqueue_script( 'visualizer-render' );
773
774 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
775 wp_enqueue_script( 'visualizer-editor-simple' );
776 wp_localize_script(
777 'visualizer-editor-simple',
778 'visualizer1',
779 array(
780 'ajax' => array(
781 'url' => admin_url( 'admin-ajax.php' ),
782 'nonces' => array(
783 ),
784 'actions' => array(
785 ),
786 ),
787 )
788 );
789 }
790
791 $table_col_mapping = $this->loadCodeEditorAssets( $this->_chart->ID );
792
793 wp_localize_script(
794 'visualizer-render',
795 'visualizer',
796 array(
797 'l10n' => array(
798 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
799 'loading' => esc_html__( 'Loading...', 'visualizer' ),
800 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
801 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
802 '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' ),
803 ),
804 'charts' => array(
805 'canvas' => $data,
806 'id' => $this->_chart->ID,
807 ),
808 'language' => $this->get_language(),
809 'map_api_key' => get_option( 'visualizer-map-api-key' ),
810 'ajax' => array(
811 'url' => admin_url( 'admin-ajax.php' ),
812 'nonces' => array(
813 'permissions' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA ),
814 'db_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION ),
815 'json_get_roots' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION ),
816 'json_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION ),
817 'json_set_schedule' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION ),
818 ),
819 'actions' => array(
820 'permissions' => Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA,
821 'db_get_data' => Visualizer_Plugin::ACTION_FETCH_DB_DATA,
822 'json_get_roots' => Visualizer_Plugin::ACTION_JSON_GET_ROOTS,
823 'json_get_data' => Visualizer_Plugin::ACTION_JSON_GET_DATA,
824 'json_set_schedule' => Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE,
825 ),
826 ),
827 'db_query' => array(
828 'tables' => $table_col_mapping,
829 ),
830 'is_pro' => Visualizer_Module::is_pro(),
831 'page_type' => 'chart',
832 'json_tag_separator' => Visualizer_Source_Json::TAG_SEPARATOR,
833 'json_tag_separator_view' => Visualizer_Source_Json::TAG_SEPARATOR_VIEW,
834 'is_front' => false,
835 )
836 );
837
838 $render = new Visualizer_Render_Page_Data();
839 $render->chart = $this->_chart;
840 $render->type = $data['type'];
841 $render->custom_css = $data['css'];
842 $render->sidebar = $sidebar;
843 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
844 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
845 ? esc_html__( 'Save Chart', 'visualizer' )
846 : esc_html__( 'Create Chart', 'visualizer' );
847 if ( filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART ) {
848 $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
849 }
850 } else {
851 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
852 }
853
854 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
855
856 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
857 global $Visualizer_Pro;
858 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
859 }
860
861 $this->_addAction( 'admin_head', 'renderFlattrScript' );
862 wp_iframe( array( $render, 'render' ) );
863 }
864
865 /**
866 * Handles chart type selection page.
867 *
868 * @since 1.0.0
869 *
870 * @access private
871 */
872 private function _handleTypesPage() {
873 // process post request
874 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
875 $type = filter_input( INPUT_POST, 'type' );
876 $library = filter_input( INPUT_POST, 'chart-library' );
877 if ( in_array( $type, Visualizer_Plugin::getChartTypes(), true ) ) {
878 if ( empty( $library ) ) {
879 // library cannot be empty.
880 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Chart library empty while creating the chart! Aborting...', 'error', __FILE__, __LINE__ );
881 return;
882 }
883
884 // save new chart type
885 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, $type );
886 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, $library );
887 // if the chart has default data, update it with appropriate default data for new type
888 if ( filter_var( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, true ), FILTER_VALIDATE_BOOLEAN ) ) {
889 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $type . '.csv' );
890 $source->fetch();
891 $this->_chart->post_content = $source->getData( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
892 wp_update_post( $this->_chart->to_array() );
893 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
894 }
895
896 Visualizer_Module_Utility::set_defaults( $this->_chart );
897
898 // redirect to next tab
899 // changed by Ash/Upwork
900 wp_redirect( esc_url_raw( add_query_arg( 'tab', 'settings' ) ) );
901
902 return;
903 }
904 }
905 $render = new Visualizer_Render_Page_Types();
906 $render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
907 $render->types = Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' );
908 $render->chart = $this->_chart;
909 wp_enqueue_style( 'visualizer-frame' );
910 wp_enqueue_script( 'visualizer-frame' );
911 wp_iframe( array( $render, 'render' ) );
912 }
913
914 /**
915 * Renders flattr script in the iframe <head>
916 *
917 * @since 1.4.2
918 * @action admin_head
919 *
920 * @access public
921 */
922 public function renderFlattrScript() {
923 echo '';
924 }
925
926 /**
927 * Processes the CSV that is sent in the request as a string.
928 *
929 * @since 3.2.0
930 */
931 private function handleCSVasString( $data, $editor_type ) {
932 $source = null;
933
934 switch ( $editor_type ) {
935 case 'text':
936 // data coming in from the text editor.
937 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
938 $handle = fopen( $tmpfile, 'w' );
939 $values = preg_split( '/[\n\r]+/', stripslashes( trim( $data ) ) );
940 if ( $values ) {
941 foreach ( $values as $row ) {
942 if ( empty( $row ) ) {
943 continue;
944 }
945 $row = explode( ',', $row );
946 $row = array_map(
947 function( $r ) {
948 return '' === $r ? ' ' : $r;
949 },
950 $row
951 );
952 $row = implode( ',', $row );
953 // don't use fpucsv here because we need to just dump the data
954 // minus the empty rows
955 // because fputcsv needs to tokenize
956 // we can standardize the CSV enclosure here and replace all ' with "
957 // we can assume that if there are an even number of ' they should be changed to "
958 // but that will screw up words like fo'c'sle
959 // so here let's just assume ' will NOT be used for enclosures
960 fwrite( $handle, $row );
961 fwrite( $handle, PHP_EOL );
962 }
963 }
964 $source = new Visualizer_Source_Csv( $tmpfile );
965 fclose( $handle );
966 break;
967 case 'table':
968 // Import from Chart
969 // fall-through.
970 case 'excel':
971 // data coming in from the excel editor.
972 $source = apply_filters( 'visualizer_pro_handle_chart_data', $data, '' );
973 break;
974 }
975 return $source;
976 }
977
978 /**
979 * Parses the data uploaded as an HTML table.
980 *
981 * @since 3.2.0
982 *
983 * @access private
984 */
985 private function handleTabularData() {
986 $csv = array();
987 // the datatable mentions the headers twice, so lets remove the duplicates.
988 $headers = array_unique( array_filter( $_POST['header'] ) );
989 $types = $_POST['type'];
990
991 // capture all the indexes that correspond to excluded columns.
992 $exclude = array();
993 $index = 0;
994 foreach ( $types as $type ) {
995 if ( empty( $type ) ) {
996 $exclude[] = $index;
997 }
998 $index++;
999 }
1000
1001 // when N headers are being renamed, the number of headers increases by N
1002 // because of the way datatable duplicates header information
1003 // so unset the headers that have been renamed.
1004 if ( count( $headers ) !== count( $types ) ) {
1005 $to = count( $headers );
1006 for ( $i = count( $types ); $i < $to; $i++ ) {
1007 unset( $headers[ $i + 1 ] );
1008 }
1009 }
1010
1011 $columns = array();
1012 for ( $i = 0; $i < count( $headers ); $i++ ) {
1013 if ( ! isset( $_POST[ 'data' . $i ] ) ) {
1014 continue;
1015 }
1016 $columns[ $i ] = $_POST[ 'data' . $i ];
1017 }
1018
1019 $csv[] = $headers;
1020 $csv[] = $types;
1021 for ( $j = 0; $j < count( $columns[0] ); $j++ ) {
1022 $row = array();
1023 for ( $i = 0; $i < count( $headers ); $i++ ) {
1024 $row[] = sanitize_text_field( $columns[ $i ][ $j ] );
1025 }
1026 $csv[] = $row;
1027 }
1028
1029 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1030 $handle = fopen( $tmpfile, 'w' );
1031
1032 if ( $csv ) {
1033 $index = 0;
1034 foreach ( $csv as $row ) {
1035 // remove all the cells corresponding to the excluded headers.
1036 foreach ( $exclude as $j ) {
1037 unset( $row[ $j ] );
1038 }
1039 fputcsv( $handle, $row );
1040 }
1041 }
1042 $source = new Visualizer_Source_Csv( $tmpfile );
1043 fclose( $handle );
1044 return $source;
1045 }
1046
1047 /**
1048 * Parses uploaded CSV file and saves new data for the chart.
1049 *
1050 * @since 1.0.0
1051 *
1052 * @access public
1053 */
1054 public function uploadData() {
1055 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1056 // otherwise, assume this is a normal web request.
1057 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1058
1059 // validate nonce
1060 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1061 if ( ! $can_die ) {
1062 return;
1063 }
1064 status_header( 403 );
1065 exit;
1066 }
1067
1068 // check chart, if chart exists
1069 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1070 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1071 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
1072 if ( ! $can_die ) {
1073 return;
1074 }
1075 status_header( 400 );
1076 exit;
1077 }
1078
1079 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__ );
1080
1081 if ( ! isset( $_POST['vz-import-time'] ) ) {
1082 apply_filters( 'visualizer_pro_remove_schedule', $chart_id );
1083 }
1084
1085 if ( ! isset( $_POST['chart_data_src'] ) || Visualizer_Plugin::CF_SOURCE_FILTER !== $_POST['chart_data_src'] ) {
1086 // delete the filters in case this chart is being uploaded from other data sources
1087 delete_post_meta( $chart_id, Visualizer_Plugin::CF_FILTER_CONFIG );
1088 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_FILTER_CONFIG );
1089 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_DB_QUERY );
1090
1091 // delete "import from db" specific parameters.
1092 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
1093 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
1094 delete_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS );
1095 }
1096
1097 // delete json related data.
1098 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL );
1099 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT );
1100 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING );
1101 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_HEADERS );
1102
1103 // delete last error
1104 delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
1105
1106 // delete editor related data.
1107 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR );
1108
1109 // delete this so that a JSON import can be later edited manually without a problem.
1110 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE );
1111
1112 $source = null;
1113 $render = new Visualizer_Render_Page_Update();
1114
1115 $remote_data = false;
1116 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1117 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1118 }
1119 if ( false !== $remote_data ) {
1120 $source = new Visualizer_Source_Csv_Remote( $remote_data );
1121 if ( isset( $_POST['vz-import-time'] ) ) {
1122 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1123 }
1124 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1125 } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
1126 $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1127 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1128 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1129 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1130 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
1131 $source = $this->handleTabularData();
1132 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1133 } else {
1134 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__ );
1135 $render->message = esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' );
1136 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' ) );
1137 }
1138
1139 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__ );
1140
1141 if ( $source ) {
1142 if ( $source->fetch() ) {
1143 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1144 $populate = true;
1145 if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {
1146 $json = unserialize( $content );
1147 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1148 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1149 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
1150 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__ );
1151 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Not populating chart data as source exists (%s) but data is empty!', $json['source'] ) );
1152 $populate = false;
1153 }
1154 }
1155 if ( $populate ) {
1156 $chart->post_content = $content;
1157 }
1158 wp_update_post( $chart->to_array() );
1159
1160 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1161 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1162 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1163
1164 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Updated post for chart %d', $chart_id ), 'debug', __FILE__, __LINE__ );
1165
1166 Visualizer_Module_Utility::set_defaults( $chart, null );
1167
1168 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1169 if ( isset( $settings['series'] ) && ! ( count( $settings['series'] ) - count( $source->getSeries() ) > 1 ) ) {
1170 $diff_total_series = abs( count( $settings['series'] ) - count( $source->getSeries() ) );
1171 if ( $diff_total_series ) {
1172 foreach ( range( 1, $diff_total_series ) as $k => $diff_series ) {
1173 $settings['series'][] = end( $settings['series'] );
1174 }
1175 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
1176 }
1177 }
1178
1179 $render->id = $chart->ID;
1180 $render->data = json_encode( $source->getRawData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1181 $render->series = json_encode( $source->getSeries() );
1182 $render->settings = json_encode( $settings );
1183 } else {
1184 $error = $source->get_error();
1185 if ( empty( $error ) ) {
1186 $error = esc_html__( 'CSV file is broken or invalid. Please try again.', 'visualizer' );
1187 }
1188 $render->message = $error;
1189 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1190 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
1191 }
1192 } else {
1193 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unknown internal error for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1194 }
1195 $render->render();
1196 if ( ! $can_die ) {
1197 return;
1198 }
1199 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1200 }
1201
1202 /**
1203 * Clones the chart.
1204 *
1205 * @since 1.0.0
1206 *
1207 * @access public
1208 */
1209 public function cloneChart() {
1210 $chart_id = $success = false;
1211 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1212 $capable = current_user_can( 'edit_posts' );
1213 if ( $nonce && $capable ) {
1214 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1215 if ( $chart_id ) {
1216 $chart = get_post( $chart_id );
1217 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1218 }
1219 }
1220 $redirect = remove_query_arg( 'vaction', wp_get_referer() );
1221 if ( $success ) {
1222 $new_chart_id = wp_insert_post(
1223 array(
1224 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1225 'post_title' => 'Visualization',
1226 'post_author' => get_current_user_id(),
1227 'post_status' => $chart->post_status,
1228 'post_content' => $chart->post_content,
1229 )
1230 );
1231 if ( is_wp_error( $new_chart_id ) ) {
1232 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__ );
1233 } else {
1234 $post_meta = get_post_meta( $chart_id );
1235 foreach ( $post_meta as $key => $value ) {
1236 if ( strpos( $key, 'visualizer-' ) !== false ) {
1237 add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
1238 }
1239 }
1240 $redirect = esc_url(
1241 add_query_arg(
1242 array(
1243 'page' => 'visualizer',
1244 'type' => filter_input( INPUT_GET, 'type' ),
1245 'vaction' => false,
1246 ),
1247 admin_url( 'admin.php' )
1248 )
1249 );
1250 }
1251 }
1252
1253 if ( defined( 'WP_TESTS_DOMAIN' ) ) {
1254 wp_die();
1255 }
1256
1257 wp_redirect( $redirect );
1258 exit;
1259 }
1260
1261 /**
1262 * Exports the chart data
1263 *
1264 * @since 1.0.0
1265 *
1266 * @access public
1267 */
1268 public function exportData() {
1269 check_ajax_referer( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION, 'security' );
1270 $capable = current_user_can( 'edit_posts' );
1271 if ( $capable ) {
1272 $chart_id = isset( $_GET['chart'] ) ? filter_var(
1273 $_GET['chart'],
1274 FILTER_VALIDATE_INT,
1275 array(
1276 'options' => array(
1277 'min_range' => 1,
1278 ),
1279 )
1280 ) : '';
1281 if ( $chart_id ) {
1282 $data = $this->_getDataAs( $chart_id, 'csv' );
1283 if ( $data ) {
1284 echo wp_send_json_success( $data );
1285 }
1286 }
1287 }
1288
1289 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1290 }
1291
1292 /**
1293 * Handles chart data page.
1294 *
1295 * @since 1.0.0
1296 *
1297 * @access private
1298 */
1299 private function _handleDataPage() {
1300 $data = $this->_getChartArray();
1301 $render = new Visualizer_Render_Page_Data();
1302 $render->chart = $this->_chart;
1303 $render->type = $data['type'];
1304
1305 if ( $data && $data['settings'] ) {
1306 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
1307 }
1308 wp_enqueue_style( 'visualizer-frame' );
1309 wp_enqueue_script( 'visualizer-render' );
1310 wp_localize_script(
1311 'visualizer-render',
1312 'visualizer',
1313 array(
1314 'l10n' => array(
1315 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
1316 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1317 ),
1318 'charts' => array(
1319 'canvas' => $data,
1320 ),
1321 )
1322 );
1323
1324 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
1325
1326 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
1327 global $Visualizer_Pro;
1328 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
1329 }
1330
1331 // Added by Ash/Upwork
1332 $this->_addAction( 'admin_head', 'renderFlattrScript' );
1333 wp_iframe( array( $render, 'render' ) );
1334 }
1335
1336 /**
1337 * Returns the data for the query.
1338 *
1339 * @access public
1340 */
1341 public function getQueryData() {
1342 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1343
1344 $params = wp_parse_args( $_POST['params'] );
1345 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1346
1347 $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1348 $html = $source->fetch( true );
1349 $error = $source->get_error();
1350 if ( ! empty( $error ) ) {
1351 wp_send_json_error( array( 'msg' => $error ) );
1352 }
1353 wp_send_json_success( array( 'table' => $html ) );
1354 }
1355
1356 /**
1357 * Saves the query and the schedule.
1358 *
1359 * @access public
1360 */
1361 public function saveQuery() {
1362 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1363
1364 $chart_id = filter_input(
1365 INPUT_GET,
1366 'chart',
1367 FILTER_VALIDATE_INT,
1368 array(
1369 'options' => array(
1370 'min_range' => 1,
1371 ),
1372 )
1373 );
1374
1375 $hours = filter_input(
1376 INPUT_POST,
1377 'refresh',
1378 FILTER_VALIDATE_FLOAT,
1379 array(
1380 'options' => array(
1381 'min_range' => -1,
1382 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1383 ),
1384 )
1385 );
1386
1387 if ( ! is_numeric( $hours ) ) {
1388 $hours = -1;
1389 }
1390
1391 $render = new Visualizer_Render_Page_Update();
1392 if ( $chart_id ) {
1393 $params = wp_parse_args( $_POST['params'] );
1394 $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1395 $source->fetch( false );
1396 $error = $source->get_error();
1397 if ( empty( $error ) ) {
1398 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $params['query'] ) );
1399 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1400 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1401 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1402 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1403 if ( isset( $params['db_type'] ) && $params['db_type'] !== Visualizer_Plugin::WP_DB_NAME ) {
1404 $remote_db_params = $params;
1405 unset( $remote_db_params['query'] );
1406 unset( $remote_db_params['chart_id'] );
1407 update_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS, $remote_db_params );
1408 }
1409
1410 $schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
1411 $schedules[ $chart_id ] = time() + $hours * HOUR_IN_SECONDS;
1412 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $schedules );
1413
1414 wp_update_post(
1415 array(
1416 'ID' => $chart_id,
1417 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
1418 )
1419 );
1420 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1421 $render->series = json_encode( $source->getSeries() );
1422 $render->id = $chart_id;
1423 } else {
1424 $render->message = $error;
1425 }
1426 }
1427 $render->render();
1428 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1429 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1430 }
1431 }
1432
1433
1434 /**
1435 * Saves the filter query and the schedule.
1436 *
1437 * @access public
1438 */
1439 public function saveFilter() {
1440 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1441
1442 $chart_id = filter_input(
1443 INPUT_GET,
1444 'chart',
1445 FILTER_VALIDATE_INT,
1446 array(
1447 'options' => array(
1448 'min_range' => 1,
1449 ),
1450 )
1451 );
1452
1453 $hours = filter_input(
1454 INPUT_POST,
1455 'refresh',
1456 FILTER_VALIDATE_INT,
1457 array(
1458 'options' => array(
1459 'min_range' => -1,
1460 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1461 ),
1462 )
1463 );
1464
1465 if ( 0 !== $hours && empty( $hours ) ) {
1466 $hours = -1;
1467 }
1468
1469 do_action( 'visualizer_save_filter', $chart_id, $hours );
1470
1471 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1472 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1473 }
1474 }
1475
1476 /**
1477 * Save chart image.
1478 *
1479 * @param string $base64_img Chart image.
1480 * @param int $chart_id Chart ID.
1481 * @param bool $save_attachment Save attachment.
1482 * @return attachment ID
1483 */
1484 public function save_chart_image( $base64_img, $chart_id, $save_attachment = true ) {
1485 // Delete old chart image.
1486 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
1487 if ( $old_attachment_id ) {
1488 wp_delete_attachment( $old_attachment_id, true );
1489 }
1490
1491 if ( ! $save_attachment ) {
1492 return 0;
1493 }
1494
1495 // Upload dir.
1496 $upload_dir = wp_upload_dir();
1497 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1498
1499 $img = str_replace( 'data:image/png;base64,', '', $base64_img );
1500 $img = str_replace( ' ', '+', $img );
1501 $decoded = base64_decode( $img );
1502 $filename = 'visualization-' . $chart_id . '.png';
1503 $file_type = 'image/png';
1504 $hashed_filename = $filename;
1505
1506 // Save the image in the uploads directory.
1507 require_once ABSPATH . '/wp-admin/includes/file.php';
1508 \WP_Filesystem();
1509 global $wp_filesystem;
1510 $upload_file = $wp_filesystem->put_contents( $upload_path . $hashed_filename, $decoded );
1511
1512 // Insert new chart image.
1513 $attachment = array(
1514 'post_mime_type' => $file_type,
1515 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $hashed_filename ) ),
1516 'post_content' => '',
1517 'post_status' => 'inherit',
1518 'guid' => $upload_dir['url'] . '/' . basename( $hashed_filename ),
1519 );
1520
1521 $attach_id = wp_insert_attachment( $attachment, $upload_dir['path'] . '/' . $hashed_filename );
1522 return $attach_id;
1523 }
1524 }
1525