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

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