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

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

588 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // +----------------------------------------------------------------------+
4 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
5 // +----------------------------------------------------------------------+
6 // | This program is free software; you can redistribute it and/or modify |
7 // | it under the terms of the GNU General Public License, version 2, as |
8 // | published by the Free Software Foundation. |
9 // | |
10 // | This program is distributed in the hope that it will be useful, |
11 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 // | GNU General Public License for more details. |
14 // | |
15 // | You should have received a copy of the GNU General Public License |
16 // | along with this program; if not, write to the Free Software |
17 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
18 // | MA 02110-1301 USA |
19 // +----------------------------------------------------------------------+
20 // | Author: Eugene Manuilov <eugene@manuilov.org> |
21 // +----------------------------------------------------------------------+
22 /**
23 * Frontend module class.
24 *
25 * @category Visualizer
26 * @package Module
27 *
28 * @since 1.0.0
29 */
30 class Visualizer_Module_Frontend extends Visualizer_Module {
31
32 const NAME = __CLASS__;
33
34 /**
35 * The array of charts to render.
36 *
37 * @since 1.0.0
38 *
39 * @access private
40 * @var array
41 */
42 private $_charts = array();
43
44 /**
45 * Constructor.
46 *
47 * @since 1.0.0
48 * @uses add_filter() To add "do_shortcode" hook for "widget_text" and "term_description" filters.
49 *
50 * @access public
51 * @param Visualizer_Plugin $plugin The instance of the plugin.
52 */
53 public function __construct( Visualizer_Plugin $plugin ) {
54 parent::__construct( $plugin );
55
56 $this->_addAction( 'wp_enqueue_scripts', 'enqueueScripts' );
57 $this->_addAction( 'visualizer_enqueue_scripts', 'enqueueScripts' );
58 $this->_addFilter( 'visualizer_get_language', 'getLanguage' );
59 $this->_addShortcode( 'visualizer', 'renderChart' );
60
61 // add do_shortocde hook for widget_text filter
62 if ( ! has_filter( 'widget_text', 'do_shortcode' ) ) {
63 add_filter( 'widget_text', 'do_shortcode' );
64 }
65
66 // add do_shortcode hook for term_description filter
67 if ( ! has_filter( 'term_description', 'do_shortcode' ) ) {
68 add_filter( 'term_description', 'do_shortcode' );
69 }
70
71 add_action( 'rest_api_init', array( $this, 'endpoint_register' ) );
72
73 $this->_addFilter( 'script_loader_tag', 'script_loader_tag', null, 10, 3 );
74 }
75
76 /**
77 * Adds the async attribute to certain scripts.
78 */
79 function script_loader_tag( $tag, $handle, $src ) {
80 if ( is_admin() ) {
81 return $tag;
82 }
83
84 $scripts = array( 'google-jsapi', 'visualizer-render-google-lib', 'visualizer-render-google' );
85
86 foreach ( $scripts as $async ) {
87 if ( $async === $handle ) {
88 $tag = str_replace( ' src', ' defer="defer" src', $tag );
89 break;
90 }
91 }
92 return $tag;
93 }
94
95 /**
96 * Returns the language/locale.
97 */
98 function getLanguage( $dummy, $only_language ) {
99 return $this->get_language();
100 }
101
102
103 /**
104 * Registers the endpoints
105 */
106 function endpoint_register() {
107 register_rest_route(
108 'visualizer/v' . VISUALIZER_REST_VERSION,
109 '/action/(?P<chart>\d+)/(?P<type>.+)/',
110 array(
111 // POST is required for save/cancel, GET for all others
112 'methods' => array( 'POST', 'GET' ),
113 'args' => array(
114 'chart' => array(
115 'required' => true,
116 'sanitize_callback' => function( $param ) {
117 return is_numeric( $param ) ? $param : null;
118 },
119 ),
120 'type' => array(
121 'required' => true,
122 'type' => 'string',
123 'enum' => array_merge( array( 'save', 'cancel' ), array_keys( $this->get_actions() ) ),
124 ),
125 ),
126 'permission_callback' => function ( WP_REST_Request $request ) {
127 $chart_id = filter_var( sanitize_text_field( $request->get_param( 'chart' ), FILTER_VALIDATE_INT ) );
128 if ( ! empty( $chart_id ) && in_array( $request->get_param( 'type' ), array( 'save', 'cancel' ), true ) ) {
129 // let save and cancel go without any check as past version of pro
130 // did not send the X-WP-Nonce
131 // we can change this at a later date.
132 return true;
133 }
134 return ! empty( $chart_id ) && apply_filters( 'visualizer_pro_show_chart', true, $chart_id );
135 },
136 'callback' => array( $this, 'perform_action' ),
137 )
138 );
139 }
140
141 /**
142 * All possible actions and their labels
143 *
144 * @access private
145 */
146 private function get_actions() {
147 $actions = apply_filters(
148 'visualizer_action_buttons',
149 array(
150 'print' => array(
151 'label' => esc_html__( 'Print', 'visualizer' ),
152 'title' => esc_html__( 'Print Chart', 'visualizer' ),
153 ),
154 'csv' => array(
155 'label' => esc_html__( 'CSV', 'visualizer' ),
156 'title' => esc_html__( 'Download as a CSV', 'visualizer' ),
157 ),
158 'xls' => array(
159 'label' => esc_html__( 'Excel', 'visualizer' ),
160 'title' => esc_html__( 'Download as a spreadsheet', 'visualizer' ),
161 ),
162 'copy' => array(
163 'label' => esc_html__( 'Copy', 'visualizer' ),
164 'title' => esc_html__( 'Copy data', 'visualizer' ),
165 ),
166 'image' => array(
167 'label' => esc_html__( 'Download', 'visualizer' ),
168 'title' => esc_html__( 'Download as an image', 'visualizer' ),
169 ),
170 )
171 );
172
173 // backward compatibility before label and title attributes were introduced.
174 if ( isset( $actions['edit'] ) && is_string( $actions['edit'] ) ) {
175 $actions['edit'] = array(
176 'label' => esc_html__( 'Edit', 'visualizer' ),
177 'title' => esc_html__( 'Edit data', 'visualizer' ),
178 );
179 }
180 return $actions;
181 }
182
183 /**
184 * The print button
185 *
186 * @since 1.0.0
187 *
188 * @access public
189 */
190 public function perform_action( WP_REST_Request $params ) {
191 $chart_id = filter_var( sanitize_text_field( $params['chart'] ), FILTER_VALIDATE_INT );
192 $type = sanitize_text_field( $params['type'] );
193 $data = null;
194
195 if ( ! $chart_id ) {
196 return new WP_REST_Response( array( 'error' => new WP_Error( __( 'Invalid chart ID', 'visualizer' ) ) ) );
197 }
198
199 if ( ! $type ) {
200 return new WP_REST_Response( array( 'error' => new WP_Error( __( 'Invalid action', 'visualizer' ) ) ) );
201 }
202
203 switch ( $type ) {
204 case 'print':
205 $data = $this->_getDataAs( $chart_id, 'print' );
206 break;
207 case 'csv':
208 $data = $this->_getDataAs( $chart_id, 'csv' );
209 break;
210 case 'xls':
211 $data = $this->_getDataAs( $chart_id, 'xls' );
212 break;
213 case 'image':
214 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
215 $title = 'visualizer#' . $chart_id;
216 if ( ! empty( $settings['title'] ) ) {
217 $title = $settings['title'];
218 }
219 // for ChartJS, title is an array.
220 if ( is_array( $title ) && isset( $title['text'] ) ) {
221 $title = $title['text'];
222 }
223 if ( empty( $title ) ) {
224 $title = 'visualizer#' . $chart_id;
225 }
226 $data = array( 'name' => $title );
227 break;
228 default:
229 $data = apply_filters( 'visualizer_action_data', $data, $chart_id, $type, $params, $this );
230 break;
231 }
232
233 return new WP_REST_Response( array( 'data' => $data ) );
234 }
235
236 /**
237 * Registers scripts for charts rendering.
238 *
239 * @since 1.0.0
240 * @uses wp_register_script To register javascript file.
241 *
242 * @access public
243 */
244 public function enqueueScripts() {
245 wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
246 wp_register_style( 'visualizer-front', VISUALIZER_ABSURL . 'css/front.css', array(), Visualizer_Plugin::VERSION );
247 do_action( 'visualizer_pro_frontend_load_resources' );
248 }
249
250 /**
251 * Returns placeholder for chart according to visualizer shortcode attributes.
252 *
253 * @since 1.0.0
254 * @uses shortcode_atts() To parse income shortocdes.
255 * @uses apply_filters() To filter chart's data and series arrays.
256 * @uses get_post_meta() To fetch chart's meta information.
257 * @uses wp_enqueue_script() To enqueue charts render script.
258 * @uses wp_localize_script() To add chart data to the page inline script.
259 *
260 * @access public
261 * @param array $atts The array of shortcode attributes.
262 */
263 public function renderChart( $atts ) {
264 global $wp_version;
265 $atts = shortcode_atts(
266 array(
267 // chart id
268 'id' => false,
269 // chart class
270 'class' => false,
271 // for lazy load
272 // set 'yes' to use the default intersection limit (300px)
273 // OR set a number (e.g. 700) to use 700px as the intersection limit
274 'lazy' => apply_filters( 'visualizer_lazy_by_default', false, $atts['id'] ),
275 // Use image chart
276 'use_image' => 'no',
277 ),
278 $atts
279 );
280
281 $chart_data = $this->getChartData( Visualizer_Plugin::CF_CHART_CACHE, $atts['id'] );
282 // if empty chart does not exists, then return empty string.
283 if ( ! $chart_data ) {
284 return '';
285 }
286
287 $chart = $chart_data['chart'];
288 $type = $chart_data['type'];
289 $series = $chart_data['series'];
290 // do not show the chart?
291 if ( ! apply_filters( 'visualizer_pro_show_chart', true, $atts['id'] ) ) {
292 return '';
293 }
294
295 // in case revisions exist.
296 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
297 if ( true === ( $revisions = $this->undoRevisions( $chart->ID, true ) ) ) {
298 $chart = get_post( $chart->ID );
299 }
300
301 $id = 'visualizer-' . $atts['id'];
302 $defaultClass = 'visualizer-front';
303 $class = apply_filters( Visualizer_Plugin::FILTER_CHART_WRAPPER_CLASS, $atts['class'], $atts['id'] );
304
305 $lazyClass = $atts['lazy'] === 'yes' || ctype_digit( $atts['lazy'] ) ? 'visualizer-lazy' : '';
306
307 $class = sprintf( '%s %s %s %s', $defaultClass, $class, 'visualizer-front-' . $atts['id'], $lazyClass );
308 $attributes = array();
309 if ( ! empty( $class ) ) {
310 $attributes['class'] = trim( $class );
311 }
312
313 if ( ctype_digit( $atts['lazy'] ) ) {
314 $attributes['data-lazy-limit'] = $atts['lazy'];
315 }
316
317 $chart = apply_filters( 'visualizer_schedule_refresh_chart', $chart, $chart->ID, false );
318
319 // Get and update settings.
320 $settings = $chart_data['settings'];
321 if ( empty( $settings['height'] ) ) {
322 $settings['height'] = '400';
323 }
324
325 // handle series filter hooks
326 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, $series, $chart->ID, $type );
327
328 // handle settings filter hooks
329 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
330
331 // handle data filter hooks
332 $data = self::get_chart_data( $chart, $type );
333
334 $css = '';
335 $arguments = $this->get_inline_custom_css( $id, $settings );
336 if ( ! empty( $arguments ) ) {
337 $css = $arguments[0];
338 $settings = $arguments[1];
339 }
340
341 $library = $this->load_chart_type( $chart->ID );
342
343 $id = $id . '-' . rand();
344
345 $amp = Visualizer_Plugin::instance()->getModule( Visualizer_Module_AMP::NAME );
346 if ( $amp && $amp->is_amp() ) {
347 return '<div data-amp-auto-lightbox-disable id="' . $id . '"' . $this->getHtmlAttributes( $attributes ) . '>' . $amp->get_chart( $chart, $data, $series, $settings ) . '</div>';
348 }
349
350 if ( 'yes' === $atts['use_image'] ) {
351 $chart_image = $chart_data['chart_image'];
352 if ( $chart_image ) {
353 return '<div id="' . $id . '"' . $this->getHtmlAttributes( $attributes ) . '>' . wp_get_attachment_image( $chart_image, 'full' ) . '</div>';
354 }
355 }
356
357 // add chart to the array
358 $this->_charts[ $id ] = array(
359 'type' => $type,
360 'series' => $series,
361 'settings' => $settings,
362 'data' => $data,
363 'library' => $library,
364 );
365
366 $actions_div = '';
367 $actions_visible = apply_filters( 'visualizer_pro_add_actions', isset( $settings['actions'] ) ? $settings['actions'] : array(), $atts['id'] );
368 if ( ! empty( $actions_visible ) ) {
369 $actions = $this->get_actions();
370 $actions_div = '<div class="visualizer-actions">';
371 foreach ( $actions_visible as $action_type ) {
372 $key = $action_type;
373 $mime = '';
374 if ( strpos( $action_type, ';' ) !== false ) {
375 $array = explode( ';', $action_type );
376 $key = $array[0];
377 $mime = end( $array );
378 }
379 $label = $actions[ $key ]['label'];
380 $title = $actions[ $key ]['title'];
381 $actions_div .= sprintf( '<a href="#" class="visualizer-action visualizer-action-%s" data-visualizer-type="%s" data-visualizer-chart-id="%s" data-visualizer-container-id="%s" data-visualizer-mime="%s" title="%s"', $key, $key, $atts['id'], $id, $mime, $title );
382
383 if ( 'copy' === $key ) {
384 $copy = $this->_getDataAs( $atts['id'], 'csv' );
385 $actions_div .= ' data-clipboard-text="' . esc_attr( $copy['csv'] ) . '"';
386 wp_enqueue_script( 'clipboard' );
387 }
388
389 $actions_div .= apply_filters( 'visualizer_action_attributes', '', $key, $atts['id'] );
390 $actions_div .= '>' . $label . '</a> &nbsp;';
391 }
392
393 $actions_div .= '</div>';
394 }
395
396 $actions_div .= $css;
397
398 foreach ( $this->_charts as $id => $array ) {
399 $library = $array['library'];
400 wp_register_script(
401 "visualizer-render-$library",
402 VISUALIZER_ABSURL . 'js/render-facade.js',
403 apply_filters( 'visualizer_assets_render', array( 'jquery', 'visualizer-customization' ), true ),
404 Visualizer_Plugin::VERSION,
405 true
406 );
407
408 wp_enqueue_script( "visualizer-render-$library" );
409 wp_localize_script(
410 "visualizer-render-$library",
411 'visualizer',
412 array(
413 'charts' => $this->_charts,
414 'language' => $this->get_language(),
415 'map_api_key' => get_option( 'visualizer-map-api-key' ),
416 'rest_url' => version_compare( $wp_version, '4.7.0', '>=' ) ? rest_url( 'visualizer/v' . VISUALIZER_REST_VERSION . '/action/#id#/#type#/' ) : '',
417 'wp_nonce' => wp_create_nonce( 'wp_rest' ),
418 'i10n' => array(
419 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
420 ),
421 'page_type' => 'frontend',
422 'is_front' => true,
423 )
424 );
425 wp_enqueue_style( 'visualizer-front' );
426 }
427
428 // return placeholder div
429 return $actions_div . '<div id="' . $id . '"' . $this->getHtmlAttributes( $attributes ) . '></div>' . $this->addSchema( $chart->ID );
430 }
431
432 /**
433 * Converts the array of attributes to a string of HTML attributes.
434 *
435 * @since ?
436 *
437 * @access private
438 */
439 private function getHtmlAttributes( $attributes ) {
440 $string = '';
441 if ( ! $attributes ) {
442 return $string;
443 }
444
445 foreach ( $attributes as $name => $value ) {
446 $string .= sprintf( '%s="%s"', esc_attr( $name ), esc_attr( $value ) );
447 }
448 return $string;
449 }
450
451 /**
452 * Adds the schema corresponding to the dataset.
453 *
454 * @since 3.3.2
455 *
456 * @access private
457 */
458 private function addSchema( $id ) {
459 // should we show informative errors as HTML comments?
460 $show_errors = apply_filters( 'visualizer_schema_show_errors', true, $id );
461
462 $settings = get_post_meta( $id, Visualizer_Plugin::CF_SETTINGS, true );
463 $title = '';
464 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
465 $title = $settings['title'];
466 if ( is_array( $title ) ) {
467 $title = $settings['title']['text'];
468 }
469 }
470 $title = apply_filters( 'visualizer_schema_name', $title, $id );
471 if ( empty( $title ) ) {
472 if ( $show_errors ) {
473 return "<!-- Not showing structured data for chart $id because title is empty -->";
474 }
475 return '';
476 }
477
478 $desc = '';
479 if ( isset( $settings['description'] ) && ! empty( $settings['description'] ) ) {
480 $desc = $settings['description'];
481 }
482 $desc = apply_filters( 'visualizer_schema_description', $desc, $id );
483 if ( empty( $desc ) ) {
484 if ( $show_errors ) {
485 return "<!-- Not showing structured data for chart $id because description is empty -->";
486 }
487 return '';
488 }
489
490 // descriptions below 50 chars are not allowed.
491 if ( strlen( $desc ) < 50 ) {
492 if ( $show_errors ) {
493 return "<!-- Not showing structured data for chart $id because description is shorter than 50 characters -->";
494 }
495 return '';
496 }
497
498 $license = '';
499 if ( isset( $settings['license'] ) && ! empty( $settings['license'] ) ) {
500 $license = $settings['license'];
501 if ( is_array( $license ) ) {
502 $license = $settings['license']['text'];
503 }
504 }
505 $license = apply_filters( 'visualizer_schema_license', $license, $id );
506 if ( empty( $license ) ) {
507 if ( $show_errors ) {
508 return "<!-- Not showing structured data for chart $id because license is empty -->";
509 }
510 return '';
511 }
512
513 $creator = '';
514 if ( isset( $settings['creator'] ) && ! empty( $settings['creator'] ) ) {
515 $creator = $settings['creator'];
516 if ( is_array( $creator ) ) {
517 $creator = $settings['creator']['text'];
518 }
519 }
520 $creator = apply_filters( 'visualizer_schema_creator', $creator, $id );
521 if ( empty( $creator ) ) {
522 if ( $show_errors ) {
523 return "<!-- Not showing structured data for chart $id because creator is empty -->";
524 }
525 return '';
526 }
527
528 $schema = apply_filters(
529 'visualizer_schema',
530 '{
531 "@context":"https://schema.org/",
532 "@type":"Dataset",
533 "name":"' . esc_html( $title ) . '",
534 "description":"' . esc_html( $desc ) . '",
535 "license": "' . esc_html( $license ) . '",
536 "creator": {
537 "@type": "Person",
538 "name": "' . esc_html( $creator ) . '"
539 }
540 }',
541 $id
542 );
543
544 if ( empty( $schema ) ) {
545 return '';
546 }
547
548 return '<script type="application/ld+json">' . $schema . '</script>';
549 }
550
551 /**
552 * Get chart by ID.
553 *
554 * @param string $cache_key Cache key.
555 * @param int $chart_id Chart ID.
556 * @return mixed
557 */
558 private function getChartData( $cache_key = '', $chart_id = 0 ) {
559 if ( ! $chart_id ) {
560 return false;
561 }
562 // Create unique cache key of each chart.
563 $cache_key .= '_' . $chart_id;
564 // Get chart from cache.
565 $chart = get_transient( $cache_key );
566 if ( $chart ) {
567 return $chart;
568 }
569
570 // Get chart by ID.
571 $chart = get_post( $chart_id );
572 if ( $chart && Visualizer_Plugin::CPT_VISUALIZER === $chart->post_type ) {
573 $chart_data = array(
574 'chart' => $chart,
575 'type' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true ),
576 'settings' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true ),
577 'series' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ),
578 'chart_image' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_IMAGE, true ),
579 );
580 // Put the results in a transient. Expire after 12 hours.
581 set_transient( $cache_key, $chart_data, apply_filters( Visualizer_Plugin::FILTER_HANDLE_CACHE_EXPIRATION_TIME, 12 * HOUR_IN_SECONDS ) );
582 return $chart_data;
583 }
584
585 return false;
586 }
587 }
588