PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.10.2
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.10.2
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 3.10.4 All 148 releases
visualizer / classes / Visualizer / Module / Chart.php

Chart.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.10.2, at classes/Visualizer/Module/Chart.php

1,605 lines 56.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Eugene Manuilov <eugene@manuilov.org> |
20 // +----------------------------------------------------------------------+
21 /**
22 * The module for all stuff related to getting, editing, creating and deleting charts.
23 *
24 * @category Visualizer
25 * @package Module
26 *
27 * @since 1.0.0
28 */
29 class Visualizer_Module_Chart extends Visualizer_Module {
30
31 const NAME = __CLASS__;
32
33 /**
34 * The chart object.
35 *
36 * @since 1.0.0
37 *
38 * @access private
39 * @var WP_Post
40 */
41 private $_chart;
42
43 /**
44 * Constructor.
45 *
46 * @since 1.0.0
47 *
48 * @access public
49 *
50 * @param Visualizer_Plugin $plugin The instance of the plugin.
51 */
52 public function __construct( Visualizer_Plugin $plugin ) {
53 parent::__construct( $plugin );
54 $this->_addAjaxAction( Visualizer_Plugin::ACTION_GET_CHARTS, 'getCharts' );
55 $this->_addAjaxAction( Visualizer_Plugin::ACTION_DELETE_CHART, 'deleteChart' );
56 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CREATE_CHART, 'renderChartPages' );
57 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EDIT_CHART, 'renderChartPages' );
58 $this->_addAjaxAction( Visualizer_Plugin::ACTION_UPLOAD_DATA, 'uploadData' );
59 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CLONE_CHART, 'cloneChart' );
60 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EXPORT_DATA, 'exportData' );
61
62 $this->_addAjaxAction( Visualizer_Plugin::ACTION_FETCH_DB_DATA, 'getQueryData' );
63 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_DB_QUERY, 'saveQuery' );
64
65 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_GET_ROOTS, 'getJsonRoots' );
66 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_GET_DATA, 'getJsonData' );
67 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_SET_DATA, 'setJsonData' );
68 $this->_addAjaxAction( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE, 'setJsonSchedule' );
69
70 $this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY, 'saveFilter' );
71
72 $this->_addFilter( 'visualizer_get_sidebar', 'getSidebar', 10, 2 );
73
74 }
75
76 /**
77 * Generates the HTML of the sidebar for the chart.
78 *
79 * @since ?
80 *
81 * @access public
82 */
83 public function getSidebar( $sidebar, $chart_id ) {
84 $chart = get_post( $chart_id );
85 $data = $this->_getChartArray( $chart );
86 $sidebar = '';
87 $sidebar_class = $this->load_chart_class_name( $chart_id );
88 if ( class_exists( $sidebar_class, true ) ) {
89 $sidebar = new $sidebar_class( $data['settings'] );
90 $sidebar->__series = $data['series'];
91 $sidebar->__data = $data['data'];
92 } else {
93 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
94 if ( $sidebar !== '' ) {
95 $sidebar->__series = $data['series'];
96 $sidebar->__data = $data['data'];
97 }
98 }
99 return str_replace( "'", '"', $sidebar->__toString() );
100 }
101
102 /**
103 * Sets the schedule for how JSON-endpoint charts should be updated.
104 *
105 * @since ?
106 *
107 * @access public
108 */
109 public function setJsonSchedule() {
110 check_ajax_referer( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION, 'security' );
111
112 $chart_id = filter_input(
113 INPUT_POST,
114 'chart',
115 FILTER_VALIDATE_INT,
116 array(
117 'options' => array(
118 'min_range' => 1,
119 ),
120 )
121 );
122
123 if ( ! $chart_id ) {
124 wp_send_json_error();
125 }
126
127 $time = filter_input(
128 INPUT_POST,
129 'time',
130 FILTER_VALIDATE_INT,
131 array(
132 'options' => array(
133 'min_range' => -1,
134 ),
135 )
136 );
137
138 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( 'Ctrl-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 if ( $this->_chart->post_status === 'auto-draft' ) {
792 $this->_chart->post_status = 'publish';
793
794 // 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.
795 // we do not want any difference in data so disable revisions temporarily.
796 $this->disableRevisionsTemporarily();
797
798 wp_update_post( $this->_chart->to_array() );
799 }
800 // save meta data only when it is NOT being canceled.
801 if ( ! ( isset( $_POST['cancel'] ) && 1 === intval( $_POST['cancel'] ) ) ) {
802 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $_POST );
803
804 // we will keep a parameter called 'internal_title' that will be set to the given title or, if empty, the chart ID
805 // this will help in searching with the chart id.
806 $settings = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
807 $title = null;
808 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
809 $title = $settings['title'];
810 if ( is_array( $title ) ) {
811 $title = $settings['title']['text'];
812 }
813 }
814 if ( empty( $title ) ) {
815 $title = $this->_chart->ID;
816 }
817 $settings['internal_title'] = $title;
818 $settings_label = isset( $settings['pieResidueSliceLabel'] ) ? $settings['pieResidueSliceLabel'] : '';
819 if ( empty( $settings_label ) ) {
820 $settings['pieResidueSliceLabel'] = esc_html__( 'Other', 'visualizer' );
821 } else {
822 $settings['pieResidueSliceLabel'] = $settings_label;
823 }
824 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
825 }
826 $render = new Visualizer_Render_Page_Send();
827 $render->text = sprintf( '[visualizer id="%d"]', $this->_chart->ID );
828 wp_iframe( array( $render, 'render' ) );
829 return;
830 }
831 $data = $this->_getChartArray();
832 $sidebar = '';
833 $sidebar_class = $this->load_chart_class_name( $this->_chart->ID );
834 if ( class_exists( $sidebar_class, true ) ) {
835 $sidebar = new $sidebar_class( $data['settings'] );
836 $sidebar->__series = $data['series'];
837 $sidebar->__data = $data['data'];
838 } else {
839 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
840 if ( $sidebar !== '' ) {
841 $sidebar->__series = $data['series'];
842 $sidebar->__data = $data['data'];
843 }
844 }
845 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
846 wp_enqueue_style( 'wp-color-picker' );
847 wp_enqueue_style( 'visualizer-frame' );
848 wp_enqueue_script( 'visualizer-preview' );
849 wp_enqueue_script( 'visualizer-chosen' );
850 wp_enqueue_script( 'visualizer-render' );
851
852 if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
853 wp_enqueue_script( 'visualizer-editor-simple' );
854 wp_localize_script(
855 'visualizer-editor-simple',
856 'visualizer1',
857 array(
858 'ajax' => array(
859 'url' => admin_url( 'admin-ajax.php' ),
860 'nonces' => array(
861 ),
862 'actions' => array(
863 ),
864 ),
865 )
866 );
867 }
868
869 $table_col_mapping = $this->loadCodeEditorAssets( $this->_chart->ID );
870
871 wp_localize_script(
872 'visualizer-render',
873 'visualizer',
874 array(
875 'l10n' => array(
876 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
877 'loading' => esc_html__( 'Loading...', 'visualizer' ),
878 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
879 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
880 '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' ),
881 ),
882 'charts' => array(
883 'canvas' => $data,
884 'id' => $this->_chart->ID,
885 ),
886 'language' => $this->get_language(),
887 'map_api_key' => get_option( 'visualizer-map-api-key' ),
888 'ajax' => array(
889 'url' => admin_url( 'admin-ajax.php' ),
890 'nonces' => array(
891 'permissions' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA ),
892 'db_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION ),
893 'json_get_roots' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_ROOTS . Visualizer_Plugin::VERSION ),
894 'json_get_data' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_GET_DATA . Visualizer_Plugin::VERSION ),
895 'json_set_schedule' => wp_create_nonce( Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE . Visualizer_Plugin::VERSION ),
896 ),
897 'actions' => array(
898 'permissions' => Visualizer_Plugin::ACTION_FETCH_PERMISSIONS_DATA,
899 'db_get_data' => Visualizer_Plugin::ACTION_FETCH_DB_DATA,
900 'json_get_roots' => Visualizer_Plugin::ACTION_JSON_GET_ROOTS,
901 'json_get_data' => Visualizer_Plugin::ACTION_JSON_GET_DATA,
902 'json_set_schedule' => Visualizer_Plugin::ACTION_JSON_SET_SCHEDULE,
903 ),
904 ),
905 'db_query' => array(
906 'tables' => $table_col_mapping,
907 ),
908 'is_pro' => Visualizer_Module::is_pro(),
909 'page_type' => 'chart',
910 'json_tag_separator' => Visualizer_Source_Json::TAG_SEPARATOR,
911 'json_tag_separator_view' => Visualizer_Source_Json::TAG_SEPARATOR_VIEW,
912 'is_front' => false,
913 'rest_base' => get_rest_url( null, 'wc/v3/reports/' ),
914 )
915 );
916
917 $render = new Visualizer_Render_Page_Data();
918 $render->chart = $this->_chart;
919 $render->type = $data['type'];
920 $render->custom_css = $data['css'];
921 $render->sidebar = $sidebar;
922 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
923 $render->button = filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART
924 ? esc_html__( 'Save Chart', 'visualizer' )
925 : esc_html__( 'Create Chart', 'visualizer' );
926 if ( filter_input( INPUT_GET, 'action' ) === Visualizer_Plugin::ACTION_EDIT_CHART ) {
927 $render->cancel_button = esc_html__( 'Cancel', 'visualizer' );
928 }
929 } else {
930 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
931 }
932
933 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
934
935 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
936 global $Visualizer_Pro;
937 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
938 }
939
940 $this->_addAction( 'admin_head', 'renderFlattrScript' );
941 wp_iframe( array( $render, 'render' ) );
942 }
943
944 /**
945 * Handles chart type selection page.
946 *
947 * @since 1.0.0
948 *
949 * @access private
950 */
951 private function _handleTypesPage() {
952 // process post request
953 if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
954 $type = filter_input( INPUT_POST, 'type' );
955 $library = filter_input( INPUT_POST, 'chart-library' );
956 if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
957 if ( empty( $library ) ) {
958 // library cannot be empty.
959 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Chart library empty while creating the chart! Aborting...', 'error', __FILE__, __LINE__ );
960 return;
961 }
962
963 // save new chart type
964 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, $type );
965 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, $library );
966 // if the chart has default data, update it with appropriate default data for new type
967 if ( filter_var( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, true ), FILTER_VALIDATE_BOOLEAN ) ) {
968 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $type . '.csv' );
969 $source->fetch();
970 $this->_chart->post_content = $source->getData( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
971 wp_update_post( $this->_chart->to_array() );
972 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
973 }
974
975 Visualizer_Module_Utility::set_defaults( $this->_chart );
976
977 // redirect to next tab
978 // changed by Ash/Upwork
979 wp_redirect( esc_url_raw( add_query_arg( 'tab', 'settings' ) ) );
980
981 return;
982 }
983 }
984 $render = new Visualizer_Render_Page_Types();
985 $render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
986 $render->types = Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' );
987 $render->chart = $this->_chart;
988 wp_enqueue_style( 'visualizer-frame' );
989 wp_enqueue_script( 'visualizer-frame' );
990 wp_iframe( array( $render, 'render' ) );
991 }
992
993 /**
994 * Renders flattr script in the iframe <head>
995 *
996 * @since 1.4.2
997 * @action admin_head
998 *
999 * @access public
1000 */
1001 public function renderFlattrScript() {
1002 echo '';
1003 }
1004
1005 /**
1006 * Processes the CSV that is sent in the request as a string.
1007 *
1008 * @since 3.2.0
1009 */
1010 private function handleCSVasString( $data, $editor_type ) {
1011 $source = null;
1012
1013 switch ( $editor_type ) {
1014 case 'text':
1015 // data coming in from the text editor.
1016 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1017 $handle = fopen( $tmpfile, 'w' );
1018 $values = preg_split( '/[\n\r]+/', stripslashes( trim( $data ) ) );
1019 if ( $values ) {
1020 foreach ( $values as $row ) {
1021 if ( empty( $row ) ) {
1022 continue;
1023 }
1024 $row = explode( ',', $row );
1025 $row = array_map(
1026 function( $r ) {
1027 return '' === $r ? ' ' : $r;
1028 },
1029 $row
1030 );
1031 $row = implode( ',', $row );
1032 // don't use fpucsv here because we need to just dump the data
1033 // minus the empty rows
1034 // because fputcsv needs to tokenize
1035 // we can standardize the CSV enclosure here and replace all ' with "
1036 // we can assume that if there are an even number of ' they should be changed to "
1037 // but that will screw up words like fo'c'sle
1038 // so here let's just assume ' will NOT be used for enclosures
1039 fwrite( $handle, $row );
1040 fwrite( $handle, PHP_EOL );
1041 }
1042 }
1043 $source = new Visualizer_Source_Csv( $tmpfile );
1044 fclose( $handle );
1045 break;
1046 case 'table':
1047 // Import from Chart
1048 // fall-through.
1049 case 'excel':
1050 // data coming in from the excel editor.
1051 $source = apply_filters( 'visualizer_pro_handle_chart_data', $data, '' );
1052 break;
1053 }
1054 return $source;
1055 }
1056
1057 /**
1058 * Parses the data uploaded as an HTML table.
1059 *
1060 * @since 3.2.0
1061 *
1062 * @access private
1063 */
1064 private function handleTabularData() {
1065 $csv = array();
1066 // the datatable mentions the headers twice, so lets remove the duplicates.
1067 $headers = array_unique( array_filter( $_POST['header'] ) );
1068 $types = $_POST['type'];
1069
1070 // capture all the indexes that correspond to excluded columns.
1071 $exclude = array();
1072 $index = 0;
1073 foreach ( $types as $type ) {
1074 if ( empty( $type ) ) {
1075 $exclude[] = $index;
1076 }
1077 $index++;
1078 }
1079
1080 // when N headers are being renamed, the number of headers increases by N
1081 // because of the way datatable duplicates header information
1082 // so unset the headers that have been renamed.
1083 if ( count( $headers ) !== count( $types ) ) {
1084 $to = count( $headers );
1085 for ( $i = count( $types ); $i < $to; $i++ ) {
1086 unset( $headers[ $i + 1 ] );
1087 }
1088 }
1089
1090 $columns = array();
1091 for ( $i = 0; $i < count( $headers ); $i++ ) {
1092 if ( ! isset( $_POST[ 'data' . $i ] ) ) {
1093 continue;
1094 }
1095 $columns[ $i ] = $_POST[ 'data' . $i ];
1096 }
1097
1098 $csv[] = $headers;
1099 $csv[] = $types;
1100 for ( $j = 0; $j < count( $columns[0] ); $j++ ) {
1101 $row = array();
1102 for ( $i = 0; $i < count( $headers ); $i++ ) {
1103 $row[] = sanitize_text_field( $columns[ $i ][ $j ] );
1104 }
1105 $csv[] = $row;
1106 }
1107
1108 $tmpfile = tempnam( get_temp_dir(), Visualizer_Plugin::NAME );
1109 $handle = fopen( $tmpfile, 'w' );
1110
1111 if ( $csv ) {
1112 $index = 0;
1113 foreach ( $csv as $row ) {
1114 // remove all the cells corresponding to the excluded headers.
1115 foreach ( $exclude as $j ) {
1116 unset( $row[ $j ] );
1117 }
1118 fputcsv( $handle, $row );
1119 }
1120 }
1121 $source = new Visualizer_Source_Csv( $tmpfile );
1122 fclose( $handle );
1123 return $source;
1124 }
1125
1126 /**
1127 * Parses uploaded CSV file and saves new data for the chart.
1128 *
1129 * @since 1.0.0
1130 *
1131 * @access public
1132 */
1133 public function uploadData() {
1134 // if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
1135 // otherwise, assume this is a normal web request.
1136 $can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
1137
1138 // validate nonce
1139 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1140 if ( ! $can_die ) {
1141 return;
1142 }
1143 status_header( 403 );
1144 exit;
1145 }
1146
1147 // check chart, if chart exists
1148 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
1149 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1150 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
1151 if ( ! $can_die ) {
1152 return;
1153 }
1154 status_header( 400 );
1155 exit;
1156 }
1157
1158 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__ );
1159
1160 if ( ! isset( $_POST['vz-import-time'] ) ) {
1161 apply_filters( 'visualizer_pro_remove_schedule', $chart_id );
1162 }
1163
1164 if ( ! isset( $_POST['chart_data_src'] ) || Visualizer_Plugin::CF_SOURCE_FILTER !== $_POST['chart_data_src'] ) {
1165 // delete the filters in case this chart is being uploaded from other data sources
1166 delete_post_meta( $chart_id, Visualizer_Plugin::CF_FILTER_CONFIG );
1167 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_FILTER_CONFIG );
1168 delete_post_meta( $chart_id, '__transient-' . Visualizer_Plugin::CF_DB_QUERY );
1169
1170 // delete "import from db" specific parameters.
1171 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY );
1172 delete_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE );
1173 delete_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS );
1174 }
1175
1176 // delete json related data.
1177 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL );
1178 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT );
1179 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING );
1180 delete_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_HEADERS );
1181
1182 // delete last error
1183 delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
1184
1185 // delete editor related data.
1186 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR );
1187
1188 // delete this so that a JSON import can be later edited manually without a problem.
1189 delete_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE );
1190
1191 $source = null;
1192 $render = new Visualizer_Render_Page_Update();
1193
1194 $remote_data = false;
1195 if ( isset( $_POST['remote_data'] ) && function_exists( 'wp_http_validate_url' ) ) {
1196 $remote_data = wp_http_validate_url( $_POST['remote_data'] );
1197 }
1198 if ( false !== $remote_data ) {
1199 $source = new Visualizer_Source_Csv_Remote( $remote_data );
1200 if ( isset( $_POST['vz-import-time'] ) ) {
1201 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
1202 }
1203 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1204 } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
1205 $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
1206 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1207 $source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1208 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1209 } elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
1210 $source = $this->handleTabularData();
1211 update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1212 } else {
1213 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__ );
1214 $render->message = esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' );
1215 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, esc_html__( 'CSV file with chart data was not uploaded. Please try again.', 'visualizer' ) );
1216 }
1217
1218 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__ );
1219
1220 if ( $source ) {
1221 if ( $source->fetch() ) {
1222 $content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
1223 $populate = true;
1224 if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {
1225 $json = unserialize( $content );
1226 // if source exists, so should data. if source exists but data is blank, do not populate the chart.
1227 // if we populate the data even if it is empty, the chart will show "Table has no columns".
1228 if ( array_key_exists( 'source', $json ) && ! empty( $json['source'] ) && ( ! array_key_exists( 'data', $json ) || empty( $json['data'] ) ) ) {
1229 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__ );
1230 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Not populating chart data as source exists (%s) but data is empty!', $json['source'] ) );
1231 $populate = false;
1232 }
1233 }
1234 if ( $populate ) {
1235 $chart->post_content = $content;
1236 }
1237 wp_update_post( $chart->to_array() );
1238
1239 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1240 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1241 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1242
1243 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Updated post for chart %d', $chart_id ), 'debug', __FILE__, __LINE__ );
1244
1245 Visualizer_Module_Utility::set_defaults( $chart, null );
1246
1247 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
1248 if ( isset( $settings['series'] ) && ! ( count( $settings['series'] ) - count( $source->getSeries() ) > 1 ) ) {
1249 $diff_total_series = abs( count( $settings['series'] ) - count( $source->getSeries() ) );
1250 if ( $diff_total_series ) {
1251 foreach ( range( 1, $diff_total_series ) as $k => $diff_series ) {
1252 $settings['series'][] = end( $settings['series'] );
1253 }
1254 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
1255 }
1256 }
1257
1258 $render->id = $chart->ID;
1259 $render->data = json_encode( $source->getRawData( get_post_meta( $chart->ID, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1260 $render->series = json_encode( $source->getSeries() );
1261 $render->settings = json_encode( $settings );
1262 } else {
1263 $error = $source->get_error();
1264 if ( empty( $error ) ) {
1265 $error = esc_html__( 'CSV file is broken or invalid. Please try again.', 'visualizer' );
1266 }
1267 $render->message = $error;
1268 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( '%s for chart %d.', $error, $chart_id ), 'error', __FILE__, __LINE__ );
1269 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, $error );
1270 }
1271 } else {
1272 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unknown internal error for chart %d.', $chart_id ), 'error', __FILE__, __LINE__ );
1273 }
1274 $render->render();
1275 if ( ! $can_die ) {
1276 return;
1277 }
1278 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1279 }
1280
1281 /**
1282 * Clones the chart.
1283 *
1284 * @since 1.0.0
1285 *
1286 * @access public
1287 */
1288 public function cloneChart() {
1289 $chart_id = $success = false;
1290 $nonce = isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], Visualizer_Plugin::ACTION_CLONE_CHART );
1291 $capable = current_user_can( 'edit_posts' );
1292 if ( $nonce && $capable ) {
1293 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
1294 if ( $chart_id ) {
1295 $chart = get_post( $chart_id );
1296 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
1297 }
1298 }
1299 $redirect = remove_query_arg( 'vaction', wp_get_referer() );
1300 if ( $success ) {
1301 $new_chart_id = wp_insert_post(
1302 array(
1303 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
1304 'post_title' => 'Visualization',
1305 'post_author' => get_current_user_id(),
1306 'post_status' => $chart->post_status,
1307 'post_content' => $chart->post_content,
1308 )
1309 );
1310 if ( is_wp_error( $new_chart_id ) ) {
1311 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__ );
1312 } else {
1313 $post_meta = get_post_meta( $chart_id );
1314 foreach ( $post_meta as $key => $value ) {
1315 if ( strpos( $key, 'visualizer-' ) !== false ) {
1316 add_post_meta( $new_chart_id, $key, maybe_unserialize( $value[0] ) );
1317 }
1318 }
1319 $redirect = esc_url(
1320 add_query_arg(
1321 array(
1322 'page' => 'visualizer',
1323 'type' => filter_input( INPUT_GET, 'type' ),
1324 'vaction' => false,
1325 ),
1326 admin_url( 'admin.php' )
1327 ),
1328 null,
1329 'db'
1330 );
1331 }
1332 }
1333
1334 if ( defined( 'WP_TESTS_DOMAIN' ) ) {
1335 wp_die();
1336 }
1337 wp_redirect( $redirect );
1338 exit;
1339 }
1340
1341 /**
1342 * Exports the chart data
1343 *
1344 * @since 1.0.0
1345 *
1346 * @access public
1347 */
1348 public function exportData() {
1349 check_ajax_referer( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION, 'security' );
1350 $capable = current_user_can( 'edit_posts' );
1351 if ( $capable ) {
1352 $chart_id = isset( $_GET['chart'] ) ? filter_var(
1353 $_GET['chart'],
1354 FILTER_VALIDATE_INT,
1355 array(
1356 'options' => array(
1357 'min_range' => 1,
1358 ),
1359 )
1360 ) : '';
1361 if ( $chart_id ) {
1362 $data = $this->_getDataAs( $chart_id, 'csv' );
1363 if ( $data ) {
1364 echo wp_send_json_success( $data );
1365 }
1366 }
1367 }
1368
1369 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1370 }
1371
1372 /**
1373 * Handles chart data page.
1374 *
1375 * @since 1.0.0
1376 *
1377 * @access private
1378 */
1379 private function _handleDataPage() {
1380 $data = $this->_getChartArray();
1381 $render = new Visualizer_Render_Page_Data();
1382 $render->chart = $this->_chart;
1383 $render->type = $data['type'];
1384
1385 if ( $data && $data['settings'] ) {
1386 unset( $data['settings']['width'], $data['settings']['height'], $data['settings']['chartArea'] );
1387 }
1388 wp_enqueue_style( 'visualizer-frame' );
1389 wp_enqueue_script( 'visualizer-render' );
1390 wp_localize_script(
1391 'visualizer-render',
1392 'visualizer',
1393 array(
1394 'l10n' => array(
1395 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
1396 'loading' => esc_html__( 'Loading...', 'visualizer' ),
1397 ),
1398 'charts' => array(
1399 'canvas' => $data,
1400 ),
1401 )
1402 );
1403
1404 do_action( 'visualizer_enqueue_scripts_and_styles', $data, $this->_chart->ID );
1405
1406 if ( Visualizer_Module::is_pro() && Visualizer_Module::is_pro_older_than( '1.9.0' ) ) {
1407 global $Visualizer_Pro;
1408 $Visualizer_Pro->_enqueueScriptsAndStyles( $data, $this->_chart->ID );
1409 }
1410
1411 // Added by Ash/Upwork
1412 $this->_addAction( 'admin_head', 'renderFlattrScript' );
1413 wp_iframe( array( $render, 'render' ) );
1414 }
1415
1416 /**
1417 * Returns the data for the query.
1418 *
1419 * @access public
1420 */
1421 public function getQueryData() {
1422 check_ajax_referer( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION, 'security' );
1423
1424 $params = wp_parse_args( $_POST['params'] );
1425 $chart_id = filter_var( $params['chart_id'], FILTER_VALIDATE_INT );
1426
1427 $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1428 $html = $source->fetch( true );
1429 $error = $source->get_error();
1430 if ( ! empty( $error ) ) {
1431 wp_send_json_error( array( 'msg' => $error ) );
1432 }
1433 wp_send_json_success( array( 'table' => $html ) );
1434 }
1435
1436 /**
1437 * Saves the query and the schedule.
1438 *
1439 * @access public
1440 */
1441 public function saveQuery() {
1442 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_DB_QUERY . Visualizer_Plugin::VERSION, 'security' );
1443
1444 $chart_id = filter_input(
1445 INPUT_GET,
1446 'chart',
1447 FILTER_VALIDATE_INT,
1448 array(
1449 'options' => array(
1450 'min_range' => 1,
1451 ),
1452 )
1453 );
1454
1455 $hours = filter_input(
1456 INPUT_POST,
1457 'refresh',
1458 FILTER_VALIDATE_FLOAT,
1459 array(
1460 'options' => array(
1461 'min_range' => -1,
1462 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1463 ),
1464 )
1465 );
1466
1467 if ( ! is_numeric( $hours ) ) {
1468 $hours = -1;
1469 }
1470
1471 $render = new Visualizer_Render_Page_Update();
1472 if ( $chart_id ) {
1473 $params = wp_parse_args( $_POST['params'] );
1474 $source = new Visualizer_Source_Query( stripslashes( $params['query'] ), $chart_id, $params );
1475 $source->fetch( false );
1476 $error = $source->get_error();
1477 if ( empty( $error ) ) {
1478 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, stripslashes( $params['query'] ) );
1479 update_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
1480 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
1481 update_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, $hours );
1482 update_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
1483 if ( isset( $params['db_type'] ) && $params['db_type'] !== Visualizer_Plugin::WP_DB_NAME ) {
1484 $remote_db_params = $params;
1485 unset( $remote_db_params['query'] );
1486 unset( $remote_db_params['chart_id'] );
1487 update_post_meta( $chart_id, Visualizer_Plugin::CF_REMOTE_DB_PARAMS, $remote_db_params );
1488 }
1489
1490 $schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
1491 $schedules[ $chart_id ] = time() + $hours * HOUR_IN_SECONDS;
1492 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $schedules );
1493
1494 wp_update_post(
1495 array(
1496 'ID' => $chart_id,
1497 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
1498 )
1499 );
1500 $render->data = json_encode( $source->getRawData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ) );
1501 $render->series = json_encode( $source->getSeries() );
1502 $render->id = $chart_id;
1503 } else {
1504 $render->message = $error;
1505 }
1506 }
1507 $render->render();
1508 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1509 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1510 }
1511 }
1512
1513
1514 /**
1515 * Saves the filter query and the schedule.
1516 *
1517 * @access public
1518 */
1519 public function saveFilter() {
1520 check_ajax_referer( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY . Visualizer_Plugin::VERSION, 'security' );
1521
1522 $chart_id = filter_input(
1523 INPUT_GET,
1524 'chart',
1525 FILTER_VALIDATE_INT,
1526 array(
1527 'options' => array(
1528 'min_range' => 1,
1529 ),
1530 )
1531 );
1532
1533 $hours = filter_input(
1534 INPUT_POST,
1535 'refresh',
1536 FILTER_VALIDATE_INT,
1537 array(
1538 'options' => array(
1539 'min_range' => -1,
1540 'max_range' => apply_filters( 'visualizer_is_business', false ) ? PHP_INT_MAX : -1,
1541 ),
1542 )
1543 );
1544
1545 if ( 0 !== $hours && empty( $hours ) ) {
1546 $hours = -1;
1547 }
1548
1549 do_action( 'visualizer_save_filter', $chart_id, $hours );
1550
1551 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
1552 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
1553 }
1554 }
1555
1556 /**
1557 * Save chart image.
1558 *
1559 * @param string $base64_img Chart image.
1560 * @param int $chart_id Chart ID.
1561 * @param bool $save_attachment Save attachment.
1562 * @return attachment ID
1563 */
1564 public function save_chart_image( $base64_img, $chart_id, $save_attachment = true ) {
1565 // Delete old chart image.
1566 $old_attachment_id = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_IMAGE, true );
1567 if ( $old_attachment_id ) {
1568 wp_delete_attachment( $old_attachment_id, true );
1569 }
1570
1571 if ( ! $save_attachment ) {
1572 return 0;
1573 }
1574
1575 // Upload dir.
1576 $upload_dir = wp_upload_dir();
1577 $upload_path = str_replace( '/', DIRECTORY_SEPARATOR, $upload_dir['path'] ) . DIRECTORY_SEPARATOR;
1578
1579 $img = str_replace( 'data:image/png;base64,', '', $base64_img );
1580 $img = str_replace( ' ', '+', $img );
1581 $decoded = base64_decode( $img );
1582 $filename = 'visualization-' . $chart_id . '.png';
1583 $file_type = 'image/png';
1584 $hashed_filename = $filename;
1585
1586 // Save the image in the uploads directory.
1587 require_once ABSPATH . '/wp-admin/includes/file.php';
1588 \WP_Filesystem();
1589 global $wp_filesystem;
1590 $upload_file = $wp_filesystem->put_contents( $upload_path . $hashed_filename, $decoded );
1591
1592 // Insert new chart image.
1593 $attachment = array(
1594 'post_mime_type' => $file_type,
1595 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $hashed_filename ) ),
1596 'post_content' => '',
1597 'post_status' => 'inherit',
1598 'guid' => $upload_dir['url'] . '/' . basename( $hashed_filename ),
1599 );
1600
1601 $attach_id = wp_insert_attachment( $attachment, $upload_dir['path'] . '/' . $hashed_filename );
1602 return $attach_id;
1603 }
1604 }
1605