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

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