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