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

1,479 lines 51.6 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 // Set current screen for the render chart.
494 set_current_screen( 'visualizer_render_chart' );
495 // check chart, if chart not exists, will create new one and redirects to the same page with proper chart id
496 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
497 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
498 $this->deleteOldCharts();
499 $default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
500 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $default_type . '.csv' );
501 $source->fetch();
502 $chart_id = wp_insert_post(
503 array(
504 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
505 'post_title' => 'Visualization',
506 'post_author' => get_current_user_id(),
507 'post_status' => 'auto-draft',
508 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
509 )
510 );
511 if ( $chart_id && ! is_wp_error( $chart_id ) ) {
512 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, $default_type );
513 add_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 1 );
514 add_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
515 add_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
516 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, '' );
517 add_post_meta(
518 $chart_id,
519 Visualizer_Plugin::CF_SETTINGS,
520 array(
521 'focusTarget' => 'datum',
522 )
523 );
524 do_action( 'visualizer_pro_new_chart_defaults', $chart_id );
525 }
526 wp_redirect( add_query_arg( 'chart', (int) $chart_id ) );
527 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
528 }
529
530 if ( isset( $_POST['chart-img'] ) && ! empty( $_POST['chart-img'] ) ) {
531 $attachment_id = $this->save_chart_image( $_POST['chart-img'], $chart_id );
532 if ( $attachment_id ) {
533 update_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, $attachment_id );
534 }
535 }
536 $lib = $this->load_chart_type( $chart_id );
537
538 // the alpha color picker (RGBA) is not supported by google.
539 $color_picker_dep = 'wp-color-picker';
540 if ( in_array( $lib, array( 'chartjs', 'datatables' ), true ) && ! wp_script_is( 'wp-color-picker-alpha', 'registered' ) ) {
541 wp_register_script( 'wp-color-picker-alpha', VISUALIZER_ABSURL . 'js/lib/wp-color-picker-alpha.min.js', array( 'wp-color-picker' ), Visualizer_Plugin::VERSION );
542 $color_picker_dep = 'wp-color-picker-alpha';
543 }
544
545 // enqueue and register scripts and styles
546 wp_register_script( 'visualizer-chosen', VISUALIZER_ABSURL . 'js/lib/chosen.jquery.min.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
547 wp_register_style( 'visualizer-chosen', VISUALIZER_ABSURL . 'css/lib/chosen.min.css', array(), Visualizer_Plugin::VERSION );
548
549 wp_register_style( 'visualizer-frame', VISUALIZER_ABSURL . 'css/frame.css', array( 'visualizer-chosen' ), Visualizer_Plugin::VERSION );
550 wp_register_script( 'visualizer-frame', VISUALIZER_ABSURL . 'js/frame.js', array( 'visualizer-chosen', 'jquery-ui-accordion', 'jquery-ui-tabs' ), Visualizer_Plugin::VERSION, true );
551 wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
552 wp_register_script(
553 'visualizer-render',
554 VISUALIZER_ABSURL . 'js/render-facade.js',
555 apply_filters( 'visualizer_assets_render', array( 'visualizer-frame', 'visualizer-customization' ), false ),
556 Visualizer_Plugin::VERSION,
557 true
558 );
559 wp_register_script(
560 'visualizer-preview',
561 VISUALIZER_ABSURL . 'js/preview.js',
562 array(
563 $color_picker_dep,
564 'visualizer-render',
565 ),
566 Visualizer_Plugin::VERSION,
567 true
568 );
569 wp_register_script( 'visualizer-editor-simple', VISUALIZER_ABSURL . 'js/simple-editor.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
570
571 do_action( 'visualizer_add_scripts' );
572
573 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
574 global $Visualizer_Pro;
575 $Visualizer_Pro->_addScriptsAndStyles();
576 }
577
578 // dispatch pages
579 $this->_chart = get_post( $chart_id );
580 $tab = isset( $_GET['tab'] ) && ! empty( $_GET['tab'] ) ? $_GET['tab'] : 'visualizer';
581
582 // skip chart type pages only for existing charts.
583 if ( VISUALIZER_SKIP_CHART_TYPE_PAGE && 'auto-draft' !== $this->_chart->post_status && ( ! empty( $_GET['tab'] ) && 'visualizer' === $_GET['tab'] ) ) {
584 $tab = 'settings';
585 }
586
587 if ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) {
588 // if the cancel button is clicked.
589 $this->undoRevisions( $chart_id, true );
590 } elseif ( isset( $_POST['save'] ) && 1 === intval( $_POST['save'] ) ) {
591 $this->handlePermissions();
592 // if the save button is clicked.
593 $this->undoRevisions( $chart_id, false );
594 } else {
595 // if the edit button is clicked.
596 $this->_chart = $this->handleExistingRevisions( $chart_id, $this->_chart );
597 }
598
599 switch ( $tab ) {
600 case 'settings':
601 $this->_handleDataAndSettingsPage();
602 break;
603 case 'type': // fall through.
604 case 'visualizer': // fall through.
605 $this->_handleTypesPage();
606 break;
607 default:
608 // this should never happen.
609 break;
610 }
611 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
612 }
613
614 /**
615 * Load code editor assets.
616 */
617 private function loadCodeEditorAssets( $chart_id ) {
618 global $wp_version;
619
620 $wp_scripts = wp_scripts();
621
622 // data tables assets.
623 wp_register_script( 'visualizer-datatables', VISUALIZER_ABSURL . 'js/lib/datatables.min.js', array( 'jquery-ui-core' ), Visualizer_Plugin::VERSION );
624 wp_register_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION );
625 wp_register_style( 'visualizer-jquery-ui', sprintf( '//ajax.googleapis.com/ajax/libs/jqueryui/%s/themes/smoothness/jquery-ui.css', $wp_scripts->registered['jquery-ui-core']->ver ), array( 'visualizer-datatables' ), Visualizer_Plugin::VERSION );
626 wp_enqueue_script( 'visualizer-datatables' );
627 wp_enqueue_style( 'visualizer-jquery-ui' );
628
629 if ( ! Visualizer_Module::is_pro() ) {
630 return;
631 }
632
633 $table_col_mapping = Visualizer_Source_Query_Params::get_all_db_tables_column_mapping( $chart_id );
634
635 if ( version_compare( $wp_version, '4.9.0', '<' ) ) {
636 // code mirror assets.
637 wp_register_script( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.js', array( 'jquery' ), Visualizer_Plugin::VERSION );
638 wp_register_script( 'visualizer-codemirror-placeholder', '//codemirror.net/addon/display/placeholder.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
639 wp_register_script( 'visualizer-codemirror-matchbrackets', '//codemirror.net/addon/edit/matchbrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
640 wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
641 wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
642 wp_register_script( 'visualizer-codemirror-sql-hint', '//codemirror.net/addon/hint/sql-hint.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
643 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 );
644 wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
645 wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
646
647 wp_enqueue_script( 'visualizer-codemirror-hint' );
648 wp_enqueue_style( 'visualizer-codemirror-hint' );
649 } else {
650 wp_enqueue_code_editor(
651 array(
652 'type' => 'sql',
653 'codemirror' => array(
654 'autofocus' => true,
655 'lineWrapping' => true,
656 'dragDrop' => false,
657 'matchBrackets' => true,
658 'autoCloseBrackets' => true,
659 'extraKeys' => array( 'Ctrl-Space' => 'autocomplete' ),
660 'hintOptions' => array( 'tables' => $table_col_mapping ),
661 ),
662 )
663 );
664 }
665
666 return $table_col_mapping;
667 }
668
669 /**
670 * Handle permissions from the new conslidated settings sidebar.
671 */
672 private function handlePermissions() {
673 if ( ! Visualizer_Module::is_pro() ) {
674 return;
675 }
676
677 // we will not support old free and new pro.
678 // handling new free and old pro.
679 if ( has_action( 'visualizer_pro_handle_tab' ) ) {
680 do_action( 'visualizer_pro_handle_tab', 'permissions', $this->_chart );
681 } else {
682 do_action( 'visualizer_handle_permissions', $this->_chart );
683 }
684 }
685
686 /**
687 * Handle data and settings page
688 */
689 private function _handleDataAndSettingsPage() {
690 if ( isset( $_POST['map_api_key'] ) ) {
691 update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
692 }
693
694 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
695 if ( $this->_chart->post_status === 'auto-draft' ) {
696 $this->_chart->post_status = 'publish';
697
698 // 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.
699 // we do not want any difference in data so disable revisions temporarily.
700 $this->disableRevisionsTemporarily();
701
702 wp_update_post( $this->_chart->to_array() );
703 }
704 // save meta data only when it is NOT being canceled.
705 if ( ! ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) ) {
706 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $_POST );
707
708 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
709 // this will help in searching with the chart id.
710 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
711 $title = null;
712 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
713 $title = $settings['title'];
714 if ( is_array( $title ) ) {
715 $title = $settings['title']['text'];
716 }
717 }
718 if ( empty( $title ) ) {
719 $title = $this->_chart->ID;
720 }
721 $settings['internal_title'] = $title;
722 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
723 }
724 $render = new Visualizer_Render_Page_Send();
725 $render->text = sprintf( '[visualizer id="%d"]', $this->_chart->ID );
726 wp_iframe( array( $render, 'render' ) );
727 return;
728 }
729 $data = $this->_getChartArray();
730 $sidebar = '';
731 $sidebar_class = $this->load_chart_class_name( $this->_chart->ID );
732 if ( class_exists( $sidebar_class, true ) ) {
733 $sidebar = new $sidebar_class( $data['settings'] );
734 $sidebar->__series = $data['series'];
735 $sidebar->__data = $data['data'];
736 } else {
737 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
738 if ( $sidebar !== '' ) {
739 $sidebar->__series = $data['series'];
740 $sidebar->__data = $data['data'];
741 }
742 }
743 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
744 wp_enqueue_style( 'wp-color-picker' );
745 wp_enqueue_style( 'visualizer-frame' );
746 wp_enqueue_script( 'visualizer-preview' );
747 wp_enqueue_script( 'visualizer-chosen' );
748 wp_enqueue_script( 'visualizer-render' );
749
750 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
751 wp_enqueue_script( 'visualizer-editor-simple' );
752 wp_localize_script(
753 'visualizer-editor-simple',
754 'visualizer1',
755 array(
756 'ajax' => array(
757 'url' => admin_url( 'admin-ajax.php' ),
758 'nonces' => array(
759 ),
760 'actions' => array(
761 ),
762 ),
763 )
764 );
765 }
766
767 $table_col_mapping = $this->loadCodeEditorAssets( $this->_chart->ID );
768
769 wp_localize_script(
770 'visualizer-render',
771 'visualizer',
772 array(
773 'l10n' => array(
774 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
775 'loading' => esc_html__( 'Loading...', 'visualizer' ),
776 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
777 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
778 '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' ),
779 ),
780 'charts' => array(
781 'canvas' => $data,
782 'id' => $this->_chart->ID,
783 ),
784 'language' => $this->get_language(),
785 'map_api_key' => get_option( 'visualizer-map-api-key' ),
786 'ajax' => array(
787 'url' => admin_url( 'admin-ajax.php' ),
788 'nonces' => array(
789 'permissions' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA ),
790 'db_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION ),
791 'json_get_roots' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION ),
792 'json_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION ),
793 'json_set_schedule' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION ),
794 ),
795 'actions' => array(
796 'permissions' => Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA,
797 'db_get_data' => Visualizer_Plugin::ACTION_FETCH_DB_DATA,
798 'json_get_roots' => Visualizer_Plugin::ACTION_JSON_GET_ROOTS,
799 'json_get_data' => Visualizer_Plugin::ACTION_JSON_GET_DATA,
800 'json_set_schedule' => Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE,
801 ),
802 ),
803 'db_query' => array(
804 'tables' => $table_col_mapping,
805 ),
806 'is_pro' => Visualizer_Module::is_pro(),
807 'page_type' => 'chart',
808 'json_tag_separator' => Visualizer_Source_Json::TAG_SEPARATOR,
809 'json_tag_separator_view' => Visualizer_Source_Json::TAG_SEPARATOR_VIEW,
810 'is_front' => false,
811 )
812 );
813
814 $render = new Visualizer_Render_Page_Data();
815 $render->chart = $this->_chart;
816 $render->type = $data['type'];
817 $render->custom_css = $data['css'];
818 $render->sidebar = $sidebar;
819 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
820 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
821 ? esc_html__( 'Save Chart', 'visualizer' )
822 : esc_html__( 'Create Chart', 'visualizer' );
823 if ( filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART ) {
824 $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
825 }
826 } else {
827 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
828 }
829
830 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
831
832 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
833 global $Visualizer_Pro;
834 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
835 }
836
837 $this->_addAction( 'admin_head', 'renderFlattrScript' );
838 wp_iframe( array( $render, 'render' ) );
839 }
840
841 /**
842 * Handles chart type selection page.
843 *
844 * @since 1.0.0
845 *
846 * @access private
847 */
848 private function _handleTypesPage() {
849 // process post request
850 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
851 $type = filter_input( INPUT_POST, 'type' );
852 $library = filter_input( INPUT_POST, 'chart-library' );
853 if ( in_array( $type, Visualizer_Plugin::getChartTypes(), true ) ) {
854 if ( empty( $library ) ) {
855 // library cannot be empty.
856 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Chart library empty while creating the chart! Aborting...', 'error', __FILE__, __LINE__ );
857 return;
858 }
859
860 // save new chart type
861 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, $type );
862 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, $library );
863 // if the chart has default data, update it with appropriate default data for new type
864 if ( filter_var( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, true ), FILTER_VALIDATE_BOOLEAN ) ) {
865 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $type . '.csv' );
866 $source->fetch();
867 $this->_chart->post_content = $source->getData( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
868 wp_update_post( $this->_chart->to_array() );
869 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
870 }
871
872 Visualizer_Module_Utility::set_defaults( $this->_chart );
873
874 // redirect to next tab
875 // changed by Ash/Upwork
876 wp_redirect( add_query_arg( 'tab', 'settings' ) );
877
878 return;
879 }
880 }
881 $render = new Visualizer_Render_Page_Types();
882 $render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
883 $render->types = Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' );
884 $render->chart = $this->_chart;
885 wp_enqueue_style( 'visualizer-frame' );
886 wp_enqueue_script( 'visualizer-frame' );
887 wp_iframe( array( $render, 'render' ) );
888 }
889
890 /**
891 * Renders flattr script in the iframe <head>
892 *
893 * @since 1.4.2
894 * @action admin_head
895 *
896 * @access public
897 */
898 public function renderFlattrScript() {
899 echo '';
900 }
901
902 /**
903 * Processes the CSV that is sent in the request as a string.
904 *
905 * @since 3.2.0
906 */
907 private function handleCSVasString( $data, $editor_type ) {
908 $source = null;
909
910 switch ( $editor_type ) {
911 case 'text':
912 // data coming in from the text editor.
913 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
914 $handle = fopen( $tmpfile, 'w' );
915 $values = preg_split( '/[\n\r]+/', stripslashes( trim( $data ) ) );
916 if ( $values ) {
917 foreach ( $values as $row ) {
918 if ( empty( $row ) ) {
919 continue;
920 }
921 $row = explode( ',', $row );
922 $row = array_map(
923 function( $r ) {
924 return '' === $r ? ' ' : $r;
925 },
926 $row
927 );
928 $row = implode( ',', $row );
929 // don't use fpucsv here because we need to just dump the data
930 // minus the empty rows
931 // because fputcsv needs to tokenize
932 // we can standardize the CSV enclosure here and replace all ' with "
933 // we can assume that if there are an even number of ' they should be changed to "
934 // but that will screw up words like fo'c'sle
935 // so here let's just assume ' will NOT be used for enclosures
936 fwrite( $handle, $row );
937 fwrite( $handle, PHP_EOL );
938 }
939 }
940 $source = new Visualizer_Source_Csv( $tmpfile );
941 fclose( $handle );
942 break;
943 case 'table':
944 // Import from Chart
945 // fall-through.
946 case 'excel':
947 // data coming in from the excel editor.
948 $source = apply_filters( 'visualizer_pro_handle_chart_data', $data, '' );
949 break;
950 }
951 return $source;
952 }
953
954 /**
955 * Parses the data uploaded as an HTML table.
956 *
957 * @since 3.2.0
958 *
959 * @access private
960 */
961 private function handleTabularData() {
962 $csv = array();
963 // the datatable mentions the headers twice, so lets remove the duplicates.
964 $headers = array_unique( array_filter( $_POST['header'] ) );
965 $types = $_POST['type'];
966
967 // capture all the indexes that correspond to excluded columns.
968 $exclude = array();
969 $index = 0;
970 foreach ( $types as $type ) {
971 if ( empty( $type ) ) {
972 $exclude[] = $index;
973 }
974 $index++;
975 }
976
977 // when N headers are being renamed, the number of headers increases by N
978 // because of the way datatable duplicates header information
979 // so unset the headers that have been renamed.
980 if ( count( $headers ) !== count( $types ) ) {
981 $to = count( $headers );
982 for ( $i = count( $types ); $i < $to; $i++ ) {
983 unset( $headers[ $i + 1 ] );
984 }
985 }
986
987 $columns = array();
988 for ( $i = 0; $i < count( $headers ); $i++ ) {
989 if ( ! isset( $_POST[ 'data' . $i ] ) ) {
990 continue;
991 }
992 $columns[ $i ] = $_POST[ 'data' . $i ];
993 }
994
995 $csv[] = $headers;
996 $csv[] = $types;
997 for ( $j = 0; $j < count( $columns[0] ); $j++ ) {
998 $row = array();
999 for ( $i = 0; $i < count( $headers ); $i++ ) {
1000 $row[] = sanitize_text_field( $columns[ $i ][ $j ] );
1001 }
1002 $csv[] = $row;
1003 }
1004
1005 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1006 $handle = fopen( $tmpfile, 'w' );
1007
1008 if ( $csv ) {
1009 $index = 0;
1010 foreach ( $csv as $row ) {
1011 // remove all the cells corresponding to the excluded headers.
1012 foreach ( $exclude as $j ) {
1013 unset( $row[ $j ] );
1014 }
1015 fputcsv( $handle, $row );
1016 }
1017 }
1018 $source = new Visualizer_Source_Csv( $tmpfile );
1019 fclose( $handle );
1020 return $source;
1021 }
1022
1023 /**
1024 * Parses uploaded CSV file and saves new data for the chart.
1025 *
1026 * @since 1.0.0
1027 *
1028 * @access public
1029 */
1030 public function uploadData() {
1031 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1032 // otherwise, assume this is a normal web request.
1033 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1034
1035 // validate nonce
1036 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1037 if ( ! $can_die ) {
1038 return;
1039 }
1040 status_header( 403 );
1041 exit;
1042 }
1043
1044 // check chart, if chart exists
1045 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1046 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1047 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
1048 if ( ! $can_die ) {
1049 return;
1050 }
1051 status_header( 400 );
1052 exit;
1053 }
1054
1055 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__ );
1056
1057 if ( ! isset( $_POST['vz-import-time'] ) ) {
1058 apply_filters( 'visualizer_pro_remove_schedule', $chart_id );
1059 }
1060
1061 if ( ! isset( $_POST['chart_data_src'] ) || Visualizer_Plugin::CF_SOURCE_FILTER !== $_POST['chart_data_src'] ) {
1062 // delete the filters in case this chart is being uploaded from other data sources
1063 delete_post_meta( $chart_id, Visualizer_Plugin::CF_FILTER_CONFIG );
1064 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_FILTER_CONFIG );
1065 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_DB_QUERY );
1066
1067 // delete "import from db" specific parameters.
1068 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
1069 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
1070 delete_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS );
1071 }
1072
1073 // delete json related data.
1074 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL );
1075 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT );
1076 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING );
1077 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_HEADERS );
1078
1079 // delete last error
1080 delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
1081
1082 // delete editor related data.
1083 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR );
1084
1085 // delete this so that a JSON import can be later edited manually without a problem.
1086 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE );
1087
1088 $source = null;
1089 $render = new Visualizer_Render_Page_Update();
1090 if ( isset( $_POST['remote_data'] ) && filter_var( $_POST['remote_data'], FILTER_VALIDATE_URL ) ) {
1091 $source = new Visualizer_Source_Csv_Remote( $_POST['remote_data'] );
1092 if ( isset( $_POST['vz-import-time'] ) ) {
1093 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $_POST['remote_data'], $_POST['vz-import-time'] );
1094 }
1095 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1096 } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
1097 $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1098 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1099 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1100 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1101 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
1102 $source = $this->handleTabularData();
1103 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1104 } else {
1105 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__ );
1106 $render->message = esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' );
1107 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' ) );
1108 }
1109
1110 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__ );
1111
1112 if ( $source ) {
1113 if ( $source->fetch() ) {
1114 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1115 $populate = true;
1116 if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {
1117 $json = unserialize( $content );
1118 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1119 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1120 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
1121 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__ );
1122 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Not populating chart data as source exists (%s) but data is empty!', $json['source'] ) );
1123 $populate = false;
1124 }
1125 }
1126 if ( $populate ) {
1127 $chart->post_content = $content;
1128 }
1129 wp_update_post( $chart->to_array() );
1130 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1131 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1132 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1133
1134 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Updated post for chart %d', $chart_id ), 'debug', __FILE__, __LINE__ );
1135
1136 Visualizer_Module_Utility::set_defaults( $chart, null );
1137
1138 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1139
1140 $render->id = $chart->ID;
1141 $render->data = json_encode( $source->getRawData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1142 $render->series = json_encode( $source->getSeries() );
1143 $render->settings = json_encode( $settings );
1144 } else {
1145 $error = $source->get_error();
1146 if ( empty( $error ) ) {
1147 $error = esc_html__( 'CSV file is broken or invalid. Please try again.', 'visualizer' );
1148 }
1149 $render->message = $error;
1150 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1151 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
1152 }
1153 } else {
1154 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unknown internal error for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1155 }
1156 $render->render();
1157 if ( ! $can_die ) {
1158 return;
1159 }
1160 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1161 }
1162
1163 /**
1164 * Clones the chart.
1165 *
1166 * @since 1.0.0
1167 *
1168 * @access public
1169 */
1170 public function cloneChart() {
1171 $chart_id = $success = false;
1172 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1173 $capable = current_user_can( 'edit_posts' );
1174 if ( $nonce && $capable ) {
1175 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1176 if ( $chart_id ) {
1177 $chart = get_post( $chart_id );
1178 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1179 }
1180 }
1181 $redirect = remove_query_arg( 'vaction', wp_get_referer() );
1182 if ( $success ) {
1183 $new_chart_id = wp_insert_post(
1184 array(
1185 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1186 'post_title' => 'Visualization',
1187 'post_author' => get_current_user_id(),
1188 'post_status' => $chart->post_status,
1189 'post_content' => $chart->post_content,
1190 )
1191 );
1192 if ( is_wp_error( $new_chart_id ) ) {
1193 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__ );
1194 } else {
1195 $post_meta = get_post_meta( $chart_id );
1196 foreach ( $post_meta as $key => $value ) {
1197 if ( strpos( $key, 'visualizer-' ) !== false ) {
1198 add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
1199 }
1200 }
1201 $redirect = add_query_arg(
1202 array(
1203 'page' => 'visualizer',
1204 'type' => filter_input( INPUT_GET, 'type' ),
1205 'vaction' => false,
1206 ),
1207 admin_url( 'admin.php' )
1208 );
1209 }
1210 }
1211
1212 if ( defined( 'WP_TESTS_DOMAIN' ) ) {
1213 wp_die();
1214 }
1215
1216 wp_redirect( $redirect );
1217 exit;
1218 }
1219
1220 /**
1221 * Exports the chart data
1222 *
1223 * @since 1.0.0
1224 *
1225 * @access public
1226 */
1227 public function exportData() {
1228 check_ajax_referer( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION, 'security' );
1229 $capable = current_user_can( 'edit_posts' );
1230 if ( $capable ) {
1231 $chart_id = isset( $_GET['chart'] ) ? filter_var(
1232 $_GET['chart'],
1233 FILTER_VALIDATE_INT,
1234 array(
1235 'options' => array(
1236 'min_range' => 1,
1237 ),
1238 )
1239 ) : '';
1240 if ( $chart_id ) {
1241 $data = $this->_getDataAs( $chart_id, 'csv' );
1242 if ( $data ) {
1243 echo wp_send_json_success( $data );
1244 }
1245 }
1246 }
1247
1248 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1249 }
1250
1251 /**
1252 * Handles chart data page.
1253 *
1254 * @since 1.0.0
1255 *
1256 * @access private
1257 */
1258 private function _handleDataPage() {
1259 $data = $this->_getChartArray();
1260 $render = new Visualizer_Render_Page_Data();
1261 $render->chart = $this->_chart;
1262 $render->type = $data['type'];
1263
1264 if ( $data && $data['settings'] ) {
1265 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
1266 }
1267 wp_enqueue_style( 'visualizer-frame' );
1268 wp_enqueue_script( 'visualizer-render' );
1269 wp_localize_script(
1270 'visualizer-render',
1271 'visualizer',
1272 array(
1273 'l10n' => array(
1274 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
1275 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1276 ),
1277 'charts' => array(
1278 'canvas' => $data,
1279 ),
1280 )
1281 );
1282
1283 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
1284
1285 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
1286 global $Visualizer_Pro;
1287 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
1288 }
1289
1290 // Added by Ash/Upwork
1291 $this->_addAction( 'admin_head', 'renderFlattrScript' );
1292 wp_iframe( array( $render, 'render' ) );
1293 }
1294
1295 /**
1296 * Returns the data for the query.
1297 *
1298 * @access public
1299 */
1300 public function getQueryData() {
1301 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1302
1303 $params = wp_parse_args( $_POST['params'] );
1304 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1305
1306 $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1307 $html = $source->fetch( true );
1308 $error = $source->get_error();
1309 if ( ! empty( $error ) ) {
1310 wp_send_json_error( array( 'msg' => $error ) );
1311 }
1312 wp_send_json_success( array( 'table' => $html ) );
1313 }
1314
1315 /**
1316 * Saves the query and the schedule.
1317 *
1318 * @access public
1319 */
1320 public function saveQuery() {
1321 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1322
1323 $chart_id = filter_input(
1324 INPUT_GET,
1325 'chart',
1326 FILTER_VALIDATE_INT,
1327 array(
1328 'options' => array(
1329 'min_range' => 1,
1330 ),
1331 )
1332 );
1333
1334 $hours = filter_input(
1335 INPUT_POST,
1336 'refresh',
1337 FILTER_VALIDATE_INT,
1338 array(
1339 'options' => array(
1340 'min_range' => -1,
1341 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1342 ),
1343 )
1344 );
1345
1346 if ( ! is_int( $hours ) ) {
1347 $hours = -1;
1348 }
1349
1350 $render = new Visualizer_Render_Page_Update();
1351 if ( $chart_id ) {
1352 $params = wp_parse_args( $_POST['params'] );
1353 $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1354 $source->fetch( false );
1355 $error = $source->get_error();
1356 if ( empty( $error ) ) {
1357 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $params['query'] ) );
1358 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1359 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1360 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1361 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1362 if ( isset( $params['db_type'] ) && $params['db_type'] !== Visualizer_Plugin::WP_DB_NAME ) {
1363 $remote_db_params = $params;
1364 unset( $remote_db_params['query'] );
1365 unset( $remote_db_params['chart_id'] );
1366 update_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS, $remote_db_params );
1367 }
1368
1369 $schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
1370 $schedules[ $chart_id ] = time() + $hours * HOUR_IN_SECONDS;
1371 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $schedules );
1372
1373 wp_update_post(
1374 array(
1375 'ID' => $chart_id,
1376 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
1377 )
1378 );
1379 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1380 $render->series = json_encode( $source->getSeries() );
1381 $render->id = $chart_id;
1382 } else {
1383 $render->message = $error;
1384 }
1385 }
1386 $render->render();
1387 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1388 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1389 }
1390 }
1391
1392
1393 /**
1394 * Saves the filter query and the schedule.
1395 *
1396 * @access public
1397 */
1398 public function saveFilter() {
1399 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1400
1401 $chart_id = filter_input(
1402 INPUT_GET,
1403 'chart',
1404 FILTER_VALIDATE_INT,
1405 array(
1406 'options' => array(
1407 'min_range' => 1,
1408 ),
1409 )
1410 );
1411
1412 $hours = filter_input(
1413 INPUT_POST,
1414 'refresh',
1415 FILTER_VALIDATE_INT,
1416 array(
1417 'options' => array(
1418 'min_range' => -1,
1419 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1420 ),
1421 )
1422 );
1423
1424 if ( 0 !== $hours && empty( $hours ) ) {
1425 $hours = -1;
1426 }
1427
1428 do_action( 'visualizer_save_filter', $chart_id, $hours );
1429
1430 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1431 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1432 }
1433 }
1434
1435 /**
1436 * Save chart image.
1437 *
1438 * @param string $base64_img Chart image.
1439 * @param int $chart_id Chart ID.
1440 * @return attachment ID
1441 */
1442 public function save_chart_image( $base64_img, $chart_id ) {
1443 // Upload dir.
1444 $upload_dir = wp_upload_dir();
1445 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1446
1447 $img = str_replace( 'data:image/png;base64,', '', $base64_img );
1448 $img = str_replace( ' ', '+', $img );
1449 $decoded = base64_decode( $img );
1450 $filename = 'visualization-' . $chart_id . '.png';
1451 $file_type = 'image/png';
1452 $hashed_filename = $filename;
1453
1454 // Delete old chart image.
1455 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
1456 if ( $old_attachment_id ) {
1457 wp_delete_attachment( $old_attachment_id, true );
1458 }
1459
1460 // Save the image in the uploads directory.
1461 require_once ABSPATH . '/wp-admin/includes/file.php';
1462 \WP_Filesystem();
1463 global $wp_filesystem;
1464 $upload_file = $wp_filesystem->put_contents( $upload_path . $hashed_filename, $decoded );
1465
1466 // Insert new chart image.
1467 $attachment = array(
1468 'post_mime_type' => $file_type,
1469 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $hashed_filename ) ),
1470 'post_content' => '',
1471 'post_status' => 'inherit',
1472 'guid' => $upload_dir['url'] . '/' . basename( $hashed_filename ),
1473 );
1474
1475 $attach_id = wp_insert_attachment( $attachment, $upload_dir['path'] . '/' . $hashed_filename );
1476 return $attach_id;
1477 }
1478 }
1479