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