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