PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.3
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.3
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 4.0.3, at classes/Visualizer/Module/Frontend.php

821 lines 26.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 * Lazy render script.
46 *
47 * @access private
48 * @var bool
49 */
50 private $lazy_render_script = true;
51
52 /**
53 * Constructor.
54 *
55 * @since 1.0.0
56 * @uses add_filter() To add "do_shortcode" hook for "widget_text" and "term_description" filters.
57 *
58 * @access public
59 * @param Visualizer_Plugin $plugin The instance of the plugin.
60 */
61 public function __construct( Visualizer_Plugin $plugin ) {
62 parent::__construct( $plugin );
63
64 $this->_addAction( 'wp_print_footer_scripts', 'printFooterScripts' );
65 $this->_addAction( 'wp_enqueue_scripts', 'enqueueScripts' );
66 $this->_addAction( 'load-index.php', 'enqueueScripts' );
67 $this->_addAction( 'load-visualizer_page_visualizer-setup-wizard', 'enqueueScripts' );
68 $this->_addAction( 'visualizer_enqueue_scripts', 'enqueueScripts' );
69 $this->_addFilter( 'visualizer_get_language', 'getLanguage' );
70 $this->_addShortcode( 'visualizer', 'renderChart' );
71
72 // add do_shortocde hook for widget_text filter
73 if ( ! has_filter( 'widget_text', 'do_shortcode' ) ) {
74 add_filter( 'widget_text', 'do_shortcode' );
75 }
76
77 // add do_shortcode hook for term_description filter
78 if ( ! has_filter( 'term_description', 'do_shortcode' ) ) {
79 add_filter( 'term_description', 'do_shortcode' );
80 }
81
82 add_action( 'rest_api_init', array( $this, 'endpoint_register' ) );
83
84 $this->_addFilter( 'script_loader_tag', 'script_loader_tag', null, 10, 3 );
85 }
86
87 /**
88 * Adds the async attribute to certain scripts.
89 */
90 public function script_loader_tag( $tag, $handle, $src ) {
91 if ( is_admin() ) {
92 return $tag;
93 }
94 // Async scripts.
95 $async_scripts = array( 'google-jsapi', 'chartjs', 'visualizer-datatables' );
96 foreach ( $async_scripts as $async ) {
97 if ( $async === $handle ) {
98 $tag = str_replace( ' src', ' async src', $tag );
99 break;
100 }
101 }
102
103 // Async scripts.
104 $scripts = array( 'dom-to-image' );
105 foreach ( array( 'async', 'defer' ) as $attr ) {
106 if ( wp_scripts()->get_data( $handle, $attr ) ) {
107 break;
108 }
109 if ( in_array( $handle, $async_scripts, true ) || false === strpos( $handle, 'visualizer-' ) ) {
110 break;
111 }
112 if ( ! preg_match( ":\s$attr(=|>|\s):", $tag ) ) {
113 $tag = preg_replace( ':(?=></script>):', " $attr", $tag, 1 );
114 if ( $this->lazy_render_script ) {
115 $tag = str_replace( ' src', ' data-visualizer-script', $tag );
116 }
117 }
118 // Only allow async or defer, not both.
119 break;
120 }
121 return $tag;
122 }
123
124 /**
125 * Returns the language/locale.
126 */
127 public function getLanguage( $dummy, $only_language ) {
128 return $this->get_language();
129 }
130
131
132 /**
133 * Registers the endpoints
134 */
135 public function endpoint_register() {
136 register_rest_route(
137 'visualizer/v' . VISUALIZER_REST_VERSION,
138 '/action/(?P<chart>\d+)/(?P<type>.+)/',
139 array(
140 // POST is required for save/cancel, GET for all others
141 'methods' => array( 'POST', 'GET' ),
142 'args' => array(
143 'chart' => array(
144 'required' => true,
145 'sanitize_callback' => function ( $param ) {
146 return is_numeric( $param ) ? $param : null;
147 },
148 ),
149 'type' => array(
150 'required' => true,
151 'type' => 'string',
152 'enum' => array_merge( array( 'save', 'cancel' ), array_keys( $this->get_actions() ) ),
153 ),
154 ),
155 'permission_callback' => function ( WP_REST_Request $request ) {
156 $chart_id = filter_var( sanitize_text_field( $request->get_param( 'chart' ), FILTER_VALIDATE_INT ) );
157 if ( ! empty( $chart_id ) && in_array( $request->get_param( 'type' ), array( 'save', 'cancel' ), true ) ) {
158 // let save and cancel go without any check as past version of pro
159 // did not send the X-WP-Nonce
160 // we can change this at a later date.
161 return true;
162 }
163 return ! empty( $chart_id ) && apply_filters( 'visualizer_pro_show_chart', true, $chart_id );
164 },
165 'callback' => array( $this, 'perform_action' ),
166 )
167 );
168 }
169
170 /**
171 * All possible actions and their labels
172 *
173 * @access private
174 */
175 private function get_actions() {
176 $actions = apply_filters(
177 'visualizer_action_buttons',
178 array(
179 'print' => array(
180 'label' => esc_html__( 'Print', 'visualizer' ),
181 'title' => esc_html__( 'Print Chart', 'visualizer' ),
182 ),
183 'csv' => array(
184 'label' => esc_html__( 'CSV', 'visualizer' ),
185 'title' => esc_html__( 'Download as a CSV', 'visualizer' ),
186 ),
187 'xls' => array(
188 'label' => esc_html__( 'Excel', 'visualizer' ),
189 'title' => esc_html__( 'Download as a spreadsheet', 'visualizer' ),
190 ),
191 'copy' => array(
192 'label' => esc_html__( 'Copy', 'visualizer' ),
193 'title' => esc_html__( 'Copy data', 'visualizer' ),
194 ),
195 'image' => array(
196 'label' => esc_html__( 'Download', 'visualizer' ),
197 'title' => esc_html__( 'Download as an image', 'visualizer' ),
198 ),
199 )
200 );
201
202 // backward compatibility before label and title attributes were introduced.
203 if ( isset( $actions['edit'] ) && is_string( $actions['edit'] ) ) {
204 $actions['edit'] = array(
205 'label' => esc_html__( 'Edit', 'visualizer' ),
206 'title' => esc_html__( 'Edit data', 'visualizer' ),
207 );
208 }
209 return $actions;
210 }
211
212 /**
213 * The print button
214 *
215 * @since 1.0.0
216 *
217 * @access public
218 */
219 public function perform_action( WP_REST_Request $params ) {
220 $chart_id = filter_var( sanitize_text_field( $params['chart'] ), FILTER_VALIDATE_INT );
221 $type = sanitize_text_field( $params['type'] );
222 $data = null;
223
224 if ( ! $chart_id ) {
225 return new WP_REST_Response( array( 'error' => new WP_Error( __( 'Invalid chart ID', 'visualizer' ) ) ) );
226 }
227
228 if ( ! $type ) {
229 return new WP_REST_Response( array( 'error' => new WP_Error( __( 'Invalid action', 'visualizer' ) ) ) );
230 }
231
232 switch ( $type ) {
233 case 'print':
234 $data = $this->_getDataAs( $chart_id, 'print' );
235 break;
236 case 'csv':
237 $data = $this->_getDataAs( $chart_id, 'csv' );
238 break;
239 case 'xls':
240 $data = $this->_getDataAs( $chart_id, 'xls' );
241 break;
242 case 'image':
243 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
244 $title = 'visualizer#' . $chart_id;
245 if ( ! empty( $settings['title'] ) ) {
246 $title = $settings['title'];
247 }
248 // for ChartJS, title is an array.
249 if ( is_array( $title ) && isset( $title['text'] ) ) {
250 $title = $title['text'];
251 }
252 if ( empty( $title ) ) {
253 $title = 'visualizer#' . $chart_id;
254 }
255 $data = array( 'name' => $title );
256 break;
257 default:
258 $data = apply_filters( 'visualizer_action_data', $data, $chart_id, $type, $params, $this );
259 break;
260 }
261
262 return new WP_REST_Response( array( 'data' => $data ) );
263 }
264
265 /**
266 * Registers scripts for charts rendering.
267 *
268 * @since 1.0.0
269 * @uses wp_register_script To register javascript file.
270 *
271 * @access public
272 */
273 public function enqueueScripts() {
274 wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
275 do_action( 'visualizer_pro_frontend_load_resources' );
276 }
277
278 /**
279 * Returns placeholder for chart according to visualizer shortcode attributes.
280 *
281 * @since 1.0.0
282 * @uses shortcode_atts() To parse income shortocdes.
283 * @uses apply_filters() To filter chart's data and series arrays.
284 * @uses get_post_meta() To fetch chart's meta information.
285 * @uses wp_enqueue_script() To enqueue charts render script.
286 * @uses wp_localize_script() To add chart data to the page inline script.
287 *
288 * @access public
289 * @param array $atts The array of shortcode attributes.
290 */
291 public function renderChart( $atts ) {
292 global $wp_version;
293 $atts = shortcode_atts(
294 array(
295 // chart id
296 'id' => false,
297 // chart class
298 'class' => false,
299 // for lazy load
300 // set 'yes' to use the default intersection limit (300px)
301 // OR set a number (e.g. 700) to use 700px as the intersection limit
302 'lazy' => apply_filters( 'visualizer_lazy_by_default', false, $atts['id'] ),
303 // Use image chart
304 'use_image' => 'no',
305 ),
306 $atts
307 );
308
309 $atts['id'] = (int) $atts['id'];
310 $atts['class'] = esc_attr( $atts['class'] );
311 $atts['lazy'] = esc_attr( $atts['lazy'] );
312 $atts['use_image'] = esc_attr( $atts['use_image'] );
313
314 global $sitepress;
315 if ( Visualizer_Module::is_pro() && ( function_exists( 'icl_get_languages' ) && $sitepress instanceof \SitePress ) ) {
316 global $sitepress;
317 $locale = icl_get_current_language();
318 $locale = strtolower( str_replace( '_', '-', $locale ) );
319 $trid = $sitepress->get_element_trid( $atts['id'], 'post_' . Visualizer_Plugin::CPT_VISUALIZER );
320 $translations = $sitepress->get_element_translations( $trid );
321 if ( isset( $translations[ $locale ] ) && is_object( $translations[ $locale ] ) ) {
322 $atts['id'] = $translations[ $locale ]->element_id;
323 }
324 }
325
326 $chart_data = $this->getChartData( Visualizer_Plugin::CF_CHART_CACHE, $atts['id'] );
327 // if empty chart does not exists, then return empty string.
328 if ( ! $chart_data ) {
329 return '';
330 }
331
332 $chart = $chart_data['chart'];
333 $type = $chart_data['type'];
334 $series = $chart_data['series'];
335 // do not show the chart?
336 if ( ! apply_filters( 'visualizer_pro_show_chart', true, $atts['id'] ) ) {
337 return '';
338 }
339
340 if ( ! is_admin() && ! empty( $chart_data['is_woocommerce_report'] ) ) {
341 return '';
342 }
343
344 // in case revisions exist (skip D3 charts to avoid reverting AI data).
345 $chart_library = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, true );
346 if ( 'd3' !== $chart_library ) {
347 $revisions = $this->undoRevisions( $chart->ID, true );
348 if ( true === $revisions ) {
349 $chart = get_post( $chart->ID );
350 }
351 }
352
353 $id = 'visualizer-' . $atts['id'];
354 $defaultClass = 'visualizer-front';
355 $class = apply_filters( Visualizer_Plugin::FILTER_CHART_WRAPPER_CLASS, $atts['class'], $atts['id'] );
356
357 $lazyClass = $atts['lazy'] === 'yes' || ctype_digit( $atts['lazy'] ) ? 'visualizer-lazy' : '';
358
359 $class = sprintf( '%s %s %s %s', $defaultClass, $class, 'visualizer-front-' . $atts['id'], $lazyClass );
360 $attributes = array();
361 if ( ! empty( $class ) ) {
362 $attributes['class'] = trim( $class );
363 }
364
365 if ( ctype_digit( $atts['lazy'] ) ) {
366 $attributes['data-lazy-limit'] = $atts['lazy'];
367 }
368
369 $attributes = apply_filters( 'visualizer_container_attributes', $attributes, $chart->ID );
370
371 $chart = apply_filters( 'visualizer_schedule_refresh_chart', $chart, $chart->ID, false );
372
373 // Get and update settings.
374 $settings = $chart_data['settings'];
375 $settings = ! empty( $settings ) ? $settings : array();
376 if ( empty( $settings['height'] ) ) {
377 $settings['height'] = '400';
378 }
379
380 // handle series filter hooks
381 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, $series, $chart->ID, $type );
382
383 // handle settings filter hooks
384 $settings = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $settings, $chart->ID, $type );
385
386 $lazy_load = isset( $settings['lazy_load_chart'] ) ? $settings['lazy_load_chart'] : false;
387 $lazy_load = apply_filters( 'visualizer_lazy_load_chart', $lazy_load, $chart->ID );
388 $container_class = 'visualizer-front-container';
389 if ( ! $lazy_load ) {
390 $this->lazy_render_script = false;
391 } else {
392 $container_class .= ' visualizer-lazy-render';
393 }
394
395 // handle data filter hooks
396 $data = self::get_chart_data( $chart, $type );
397
398 $css = '';
399 $arguments = $this->get_inline_custom_css( $id, $settings );
400 if ( ! empty( $arguments ) ) {
401 $css = $arguments[0];
402 $settings = $arguments[1];
403 }
404
405 $library = $this->load_chart_type( $chart->ID );
406
407 $id = $id . '-' . rand();
408
409 $amp = Visualizer_Plugin::instance()->getModule( Visualizer_Module_AMP::NAME );
410 if ( $amp && $amp->is_amp() ) {
411 return '<div data-amp-auto-lightbox-disable id="' . $id . '"' . $this->getHtmlAttributes( $attributes ) . '>' . $amp->get_chart( $chart, $data, $series, $settings ) . '</div>';
412 }
413
414 if ( 'yes' === $atts['use_image'] ) {
415 $chart_image = $chart_data['chart_image'];
416 if ( $chart_image ) {
417 return '<div id="' . $id . '"' . $this->getHtmlAttributes( $attributes ) . '>' . wp_get_attachment_image( $chart_image, 'full' ) . '</div>';
418 }
419 }
420 // Unset chart image base64 string.
421 if ( isset( $settings['chart-img'] ) ) {
422 unset( $settings['chart-img'] );
423 }
424
425 $enable_controls = false;
426 if ( isset( $settings['controls'] ) && ! empty( $settings['controls']['controlType'] ) ) {
427 $column_index = $settings['controls']['filterColumnIndex'];
428 $column_label = $settings['controls']['filterColumnLabel'];
429 if ( 'false' !== $column_index || 'false' !== $column_label ) {
430 $enable_controls = true;
431 }
432 }
433
434 if ( ! $enable_controls ) {
435 unset( $settings['controls'] );
436 }
437
438 // add chart to the array
439 $chart_entry = array(
440 'type' => $type,
441 'series' => $series,
442 'settings' => $settings,
443 'data' => $data,
444 'library' => $library,
445 );
446
447 // For D3 charts, include the stored code so the renderer can execute it.
448 if ( 'd3' === $library ) {
449 $chart_entry['code'] = get_post_meta( $chart->ID, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
450 }
451
452 $this->_charts[ $id ] = $chart_entry;
453
454 $actions_div = '';
455 $actions_visible = apply_filters( 'visualizer_pro_add_actions', isset( $settings['actions'] ) ? $settings['actions'] : array(), $atts['id'] );
456 if ( ! empty( $actions_visible ) ) {
457 $actions = $this->get_actions();
458 $actions_div = '<div class="visualizer-actions">';
459 foreach ( $actions_visible as $action_type ) {
460 $key = $action_type;
461 $mime = '';
462 if ( strpos( $action_type, ';' ) !== false ) {
463 $array = explode( ';', $action_type );
464 $key = $array[0];
465 $mime = end( $array );
466 }
467 $label = $actions[ $key ]['label'];
468 $title = $actions[ $key ]['title'];
469 $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 );
470
471 if ( 'copy' === $key ) {
472 $copy = $this->_getDataAs( $atts['id'], 'csv' );
473 $actions_div .= ' data-clipboard-text="' . esc_attr( $copy['csv'] ) . '"';
474 wp_enqueue_script( 'clipboard' );
475 }
476
477 $actions_div .= apply_filters( 'visualizer_action_attributes', '', $key, $atts['id'] );
478 $actions_div .= '>' . $label . '</a> &nbsp;';
479 }
480
481 $actions_div .= '</div>';
482 }
483
484 $actions_div .= $css;
485
486 $_charts = array();
487 $_charts_type = '';
488 $count = 0;
489 static $visualizer_localized = false;
490 static $visualizer_charts = array();
491 static $d3_localized = false;
492 $facade_handle = 'visualizer-render-facade';
493 foreach ( $this->_charts as $id => $array ) {
494 $_charts = $this->_charts;
495 $library = $array['library'];
496 $_charts_type = $library;
497 $facade_deps = apply_filters( 'visualizer_assets_render', array( 'jquery', 'visualizer-customization' ), true );
498 if ( ! wp_script_is( $facade_handle, 'registered' ) ) {
499 wp_register_script(
500 $facade_handle,
501 VISUALIZER_ABSURL . 'js/render-facade.js',
502 $facade_deps,
503 Visualizer_Plugin::VERSION,
504 true
505 );
506 wp_enqueue_script( $facade_handle );
507 } else {
508 // Ensure newly discovered dependencies are attached to the facade.
509 global $wp_scripts;
510 if ( isset( $wp_scripts->registered[ $facade_handle ] ) ) {
511 $wp_scripts->registered[ $facade_handle ]->deps = array_unique(
512 array_merge( $wp_scripts->registered[ $facade_handle ]->deps, $facade_deps )
513 );
514 }
515 foreach ( $facade_deps as $dep_handle ) {
516 wp_enqueue_script( $dep_handle );
517 }
518 }
519
520 // For D3 charts, also enqueue the dedicated renderer bundle.
521 if ( 'd3' === $library ) {
522 $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php';
523 if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
524 /**
525 * Ignore missing build asset in source checkout.
526 *
527 * @phpstan-ignore-next-line
528 */
529 $d3_asset = include $d3_renderer_asset;
530 wp_register_script(
531 'visualizer-d3-renderer',
532 VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js',
533 array_merge( $d3_asset['dependencies'], array( 'jquery' ) ),
534 $d3_asset['version'],
535 true
536 );
537 wp_enqueue_script( 'visualizer-d3-renderer' );
538 }
539 if ( ! $d3_localized ) {
540 wp_localize_script(
541 'visualizer-d3-renderer',
542 'vizD3Renderer',
543 array(
544 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js',
545 )
546 );
547 $d3_localized = true;
548 }
549 }
550
551 if ( wp_script_is( $facade_handle ) && 0 === $count ) {
552 if ( ! $visualizer_localized ) {
553 $visualizer_charts = $this->_charts;
554 wp_localize_script(
555 $facade_handle,
556 'visualizer',
557 array(
558 'charts' => $this->_charts,
559 'language' => $this->get_language(),
560 'map_api_key' => get_option( 'visualizer-map-api-key' ),
561 'rest_url' => version_compare( $wp_version, '4.7.0', '>=' ) ? rest_url( 'visualizer/v' . VISUALIZER_REST_VERSION . '/action/#id#/#type#/' ) : '',
562 'wp_nonce' => wp_create_nonce( 'wp_rest' ),
563 'i10n' => array(
564 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
565 ),
566 'page_type' => 'frontend',
567 'is_front' => true,
568 )
569 );
570 $visualizer_localized = true;
571 } else {
572 $new_charts = array_diff_key( $this->_charts, $visualizer_charts );
573 if ( ! empty( $new_charts ) ) {
574 wp_add_inline_script(
575 $facade_handle,
576 'window.visualizer = window.visualizer || {};' .
577 'window.visualizer.charts = Object.assign(window.visualizer.charts || {}, ' . wp_json_encode( $new_charts ) . ');',
578 'before'
579 );
580 $visualizer_charts = array_merge( $visualizer_charts, $new_charts );
581 }
582 }
583 }
584 ++$count;
585 }
586 $prefix = 'C' . 'h' . 'a' . 'rt';
587 if ( $type === 'tabular' ) {
588 $prefix = 'T' . 'a' . 'bl' . 'e';
589 }
590 if ( Visualizer_Module::is_pro() && $enable_controls ) {
591 $actions_div .= '<div id="control_wrapper_' . $id . '"></div>';
592 }
593 // return placeholder div
594 return '<div class="' . $container_class . '" id="chart_wrapper_' . $id . '">' . $actions_div . '<div id="' . $id . '"' . $this->getHtmlAttributes( $attributes ) . '></div>' . $this->addSchema( $chart->ID ) . '</div>';
595 }
596
597 /**
598 * Converts the array of attributes to a string of HTML attributes.
599 *
600 * @since ?
601 *
602 * @access private
603 */
604 private function getHtmlAttributes( $attributes ) {
605 $string = '';
606 if ( ! $attributes ) {
607 return $string;
608 }
609
610 foreach ( $attributes as $name => $value ) {
611 $string .= sprintf( ' %s="%s"', esc_attr( $name ), esc_attr( $value ) );
612 }
613 return $string;
614 }
615
616 /**
617 * Adds the schema corresponding to the dataset.
618 *
619 * @since 3.3.2
620 *
621 * @access private
622 */
623 private function addSchema( $id ) {
624 // should we show informative errors as HTML comments?
625 $show_errors = apply_filters( 'visualizer_schema_show_errors', true, $id );
626
627 $settings = get_post_meta( $id, Visualizer_Plugin::CF_SETTINGS, true );
628 $title = '';
629 if ( isset( $settings['title'] ) && ! empty( $settings['title'] ) ) {
630 $title = $settings['title'];
631 if ( is_array( $title ) ) {
632 $title = $settings['title']['text'];
633 }
634 }
635 $title = apply_filters( 'visualizer_schema_name', $title, $id );
636 if ( empty( $title ) ) {
637 if ( $show_errors ) {
638 return "<!-- Not showing structured data for chart $id because title is empty -->";
639 }
640 return '';
641 }
642
643 $desc = '';
644 if ( isset( $settings['description'] ) && ! empty( $settings['description'] ) ) {
645 $desc = $settings['description'];
646 }
647 $desc = apply_filters( 'visualizer_schema_description', $desc, $id );
648 if ( empty( $desc ) ) {
649 if ( $show_errors ) {
650 return "<!-- Not showing structured data for chart $id because description is empty -->";
651 }
652 return '';
653 }
654
655 // descriptions below 50 chars are not allowed.
656 if ( strlen( $desc ) < 50 ) {
657 if ( $show_errors ) {
658 return "<!-- Not showing structured data for chart $id because description is shorter than 50 characters -->";
659 }
660 return '';
661 }
662
663 $license = '';
664 if ( isset( $settings['license'] ) && ! empty( $settings['license'] ) ) {
665 $license = $settings['license'];
666 if ( is_array( $license ) ) {
667 $license = $settings['license']['text'];
668 }
669 }
670 $license = apply_filters( 'visualizer_schema_license', $license, $id );
671 if ( empty( $license ) ) {
672 if ( $show_errors ) {
673 return "<!-- Not showing structured data for chart $id because license is empty -->";
674 }
675 return '';
676 }
677
678 $creator = '';
679 if ( isset( $settings['creator'] ) && ! empty( $settings['creator'] ) ) {
680 $creator = $settings['creator'];
681 if ( is_array( $creator ) ) {
682 $creator = $settings['creator']['text'];
683 }
684 }
685 $creator = apply_filters( 'visualizer_schema_creator', $creator, $id );
686 if ( empty( $creator ) ) {
687 if ( $show_errors ) {
688 return "<!-- Not showing structured data for chart $id because creator is empty -->";
689 }
690 return '';
691 }
692
693 $schema = apply_filters(
694 'visualizer_schema',
695 '{
696 "@context":"https://schema.org/",
697 "@type":"Dataset",
698 "name":"' . esc_html( $title ) . '",
699 "description":"' . esc_html( $desc ) . '",
700 "license": "' . esc_html( $license ) . '",
701 "creator": {
702 "@type": "Person",
703 "name": "' . esc_html( $creator ) . '"
704 }
705 }',
706 $id
707 );
708
709 if ( empty( $schema ) ) {
710 return '';
711 }
712
713 return '<script type="application/ld+json">' . $schema . '</script>';
714 }
715
716 /**
717 * Get chart by ID.
718 *
719 * @param string $cache_key Cache key.
720 * @param int $chart_id Chart ID.
721 * @return mixed
722 */
723 private function getChartData( $cache_key = '', $chart_id = 0 ) {
724 if ( ! $chart_id ) {
725 return false;
726 }
727 // Create unique cache key of each chart.
728 $cache_key .= '_' . $chart_id;
729 // Get chart from cache.
730 $chart = get_transient( $cache_key );
731 if ( $chart ) {
732 return $chart;
733 }
734
735 // Get chart by ID.
736 $chart = get_post( $chart_id );
737 if ( $chart && Visualizer_Plugin::CPT_VISUALIZER === $chart->post_type ) {
738 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
739 $series = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true );
740 $is_woocommerce_report = get_post_meta( $chart->ID, Visualizer_Plugin::CF_IS_WOOCOMMERCE_SOURCE, true );
741
742 if ( isset( $settings['series'] ) && ! ( count( $settings['series'] ) - count( $series ) > 1 ) ) {
743 $diff_total_series = abs( count( $settings['series'] ) - count( $series ) );
744 if ( $diff_total_series ) {
745 foreach ( range( 1, $diff_total_series ) as $k => $diff_series ) {
746 $settings['series'][] = end( $settings['series'] );
747 }
748 }
749 }
750 $chart_data = array(
751 'chart' => $chart,
752 'type' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true ),
753 'settings' => $settings,
754 'series' => $series,
755 'chart_image' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_IMAGE, true ),
756 'is_woocommerce_report' => $is_woocommerce_report,
757 );
758
759 // Put the results in a transient. Expire after 12 hours.
760 set_transient( $cache_key, $chart_data, apply_filters( Visualizer_Plugin::FILTER_HANDLE_CACHE_EXPIRATION_TIME, 12 * HOUR_IN_SECONDS ) );
761 return $chart_data;
762 }
763
764 return false;
765 }
766
767 /**
768 * Print footer script.
769 */
770 public function printFooterScripts() {
771 if ( $this->lazy_render_script ) {
772 ?>
773 <script type="text/javascript">
774 var visualizerUserInteractionEvents = [
775 "scroll",
776 "mouseover",
777 "keydown",
778 "touchmove",
779 "touchstart"
780 ];
781
782 visualizerUserInteractionEvents.forEach(function(event) {
783 window.addEventListener(event, visualizerTriggerScriptLoader, { passive: true });
784 });
785
786 function visualizerTriggerScriptLoader() {
787 visualizerLoadScripts();
788 visualizerUserInteractionEvents.forEach(function(event) {
789 window.removeEventListener(event, visualizerTriggerScriptLoader, { passive: true });
790 });
791 }
792
793 function visualizerLoadScripts() {
794 document.querySelectorAll("script[data-visualizer-script]").forEach(function(elem) {
795 jQuery.getScript( elem.getAttribute("data-visualizer-script") )
796 .done( function( script, textStatus ) {
797 elem.setAttribute("src", elem.getAttribute("data-visualizer-script"));
798 elem.removeAttribute("data-visualizer-script");
799 setTimeout( function() {
800 visualizerRefreshChart();
801 } );
802 } );
803 });
804 }
805
806 function visualizerRefreshChart() {
807 jQuery( '.visualizer-front:not(.visualizer-chart-loaded)' ).resize();
808 if ( jQuery( 'div.viz-facade-loaded:not(.visualizer-lazy):empty' ).length > 0 ) {
809 visualizerUserInteractionEvents.forEach( function( event ) {
810 window.addEventListener( event, function() {
811 jQuery( '.visualizer-front:not(.visualizer-chart-loaded)' ).resize();
812 }, { passive: true } );
813 } );
814 }
815 }
816 </script>
817 <?php
818 }
819 }
820 }
821