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
visualizer / classes / Visualizer / Gutenberg / Block.php

Block.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.8, at classes/Visualizer/Gutenberg/Block.php

533 lines 17.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2018 ThemeIsle (email : friends@themeisle.com) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Hardeep Asrani <hardeep@themeisle.com> |
20 // +----------------------------------------------------------------------+
21 /**
22 * All the Gutenberg block related code.
23 *
24 * @category Visualizer
25 * @package Gutenberg
26 *
27 * @since 3.1.0
28 */
29 class Visualizer_Gutenberg_Block {
30
31 /**
32 * A reference to an instance of this class.
33 *
34 * @var Visualizer_Gutenberg_Block The one Visualizer_Gutenberg_Block instance.
35 */
36 private static $instance;
37
38 /**
39 * Visualizer plugin version.
40 *
41 * @var string $version The current version of the plugin.
42 */
43 protected $version;
44
45 /**
46 * Returns an instance of this class.
47 */
48 public static function get_instance() {
49 if ( null === self::$instance ) {
50 self::$instance = new Visualizer_Gutenberg_Block();
51 }
52 return self::$instance;
53 }
54
55 /**
56 * Initializes the plugin by setting filters and administration functions.
57 */
58 private function __construct() {
59 $this->version = Visualizer_Plugin::VERSION;
60 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_gutenberg_scripts' ) );
61 add_action( 'init', array( $this, 'register_block_type' ) );
62 add_action( 'rest_api_init', array( $this, 'register_rest_endpoints' ) );
63 add_filter( 'rest_visualizer_query', array( $this, 'add_rest_query_vars' ), 9, 2 );
64 }
65
66 /**
67 * Enqueue Gutenberg block assets.
68 */
69 public function enqueue_gutenberg_scripts() {
70 global $pagenow;
71
72 $blockPath = VISUALIZER_ABSURL . 'classes/Visualizer/Gutenberg/build/index.js';
73 $stylePath = VISUALIZER_ABSURL . 'classes/Visualizer/Gutenberg/build/style-index.css';
74 $asset_path = VISUALIZER_ABSPATH . '/classes/Visualizer/Gutenberg/build/index.asset.php';
75 if ( file_exists( $asset_path ) ) {
76 // @phpstan-ignore-next-line
77 $asset = require $asset_path;
78 } else {
79 $asset = array(
80 'dependencies' => array(),
81 'version' => $this->version,
82 );
83 }
84
85 if ( VISUALIZER_TEST_JS_CUSTOMIZATION ) {
86 $asset['version'] = filemtime( VISUALIZER_ABSPATH . '/classes/Visualizer/Gutenberg/build/index.js' );
87 }
88
89 if ( ! wp_script_is( 'visualizer-datatables', 'registered' ) ) {
90 wp_register_script( 'visualizer-datatables', VISUALIZER_ABSURL . 'js/lib/datatables.min.js', array( 'jquery-ui-core' ), Visualizer_Plugin::VERSION );
91 }
92
93 if ( ! wp_style_is( 'visualizer-datatables', 'registered' ) ) {
94 wp_register_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION );
95 }
96
97 // Enqueue the bundled block JS file
98 $script_deps = array(
99 'wp-api',
100 'wp-blocks',
101 'wp-block-editor',
102 'wp-components',
103 'wp-editor',
104 'wp-element',
105 'wp-i18n',
106 'lodash',
107 'moment',
108 'react',
109 'visualizer-datatables',
110 );
111 if ( isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ) {
112 $script_deps = array_merge( $script_deps, $asset['dependencies'] );
113 }
114 $script_deps = array_values( array_unique( $script_deps ) );
115 wp_enqueue_script( 'visualizer-gutenberg-block', $blockPath, $script_deps, $asset['version'], true );
116
117 $translation_array = array(
118 'adminPage' => menu_page_url( 'visualizer', false ),
119 'createChart' => add_query_arg( array( 'action' => 'visualizer-create-chart', 'library' => 'yes', 'type' => '', 'chart-library' => '', 'tab' => 'visualizer' ), admin_url( 'admin-ajax.php' ) ),
120 'chartsPerPage' => defined( 'TI_E2E_TESTING' ) ? 20 : 6,
121 'isFullSiteEditor' => 'site-editor.php' === $pagenow,
122 /* translators: %1$s: opening tag, %2$s: closing tag */
123 'chartEditUrl' => admin_url( 'admin-ajax.php' ),
124 );
125 wp_localize_script( 'visualizer-gutenberg-block', 'visualizerLocalize', $translation_array );
126
127 $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php';
128 if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
129 // @phpstan-ignore-next-line
130 $d3_asset = include $d3_renderer_asset;
131 wp_register_script(
132 'visualizer-d3-renderer',
133 VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js',
134 array_merge( $d3_asset['dependencies'], array( 'jquery' ) ),
135 $d3_asset['version'],
136 true
137 );
138 }
139 if ( wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) {
140 wp_enqueue_script( 'visualizer-d3-renderer' );
141 wp_localize_script(
142 'visualizer-d3-renderer',
143 'vizD3Renderer',
144 array(
145 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js',
146 )
147 );
148 }
149
150 // Enqueue frontend and editor block styles
151 wp_enqueue_style( 'visualizer-gutenberg-block', $stylePath, array( 'visualizer-datatables' ), $asset['version'] );
152
153 // Enqueue ChartBuilder (AI Builder) so the D3 edit modal is available in the block editor.
154 $chart_builder_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/ChartBuilder/build/index.asset.php';
155 if ( file_exists( $chart_builder_asset ) && ! wp_script_is( 'visualizer-chart-builder', 'enqueued' ) ) {
156 /**
157 * Ignore missing build asset in source checkout.
158 *
159 * @phpstan-ignore-next-line
160 */
161 $cb_asset = include $chart_builder_asset;
162 wp_enqueue_script(
163 'visualizer-chart-builder',
164 VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/index.js',
165 $cb_asset['dependencies'],
166 $cb_asset['version'],
167 true
168 );
169 wp_enqueue_style(
170 'visualizer-chart-builder',
171 VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/style-index.css',
172 array(),
173 $cb_asset['version']
174 );
175 $chart_builder_css = VISUALIZER_ABSPATH . '/classes/Visualizer/ChartBuilder/build/index.css';
176 if ( file_exists( $chart_builder_css ) ) {
177 wp_enqueue_style(
178 'visualizer-chart-builder-runtime',
179 VISUALIZER_ABSURL . 'classes/Visualizer/ChartBuilder/build/index.css',
180 array( 'visualizer-chart-builder' ),
181 $cb_asset['version']
182 );
183 }
184 wp_localize_script(
185 'visualizer-chart-builder',
186 'vizAIBuilder',
187 array(
188 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
189 'nonce' => wp_create_nonce( 'visualizer-ai-builder' ),
190 'isPro' => Visualizer_Module::is_pro(),
191 )
192 );
193 // Inject the mount point the ChartBuilder React app needs.
194 wp_add_inline_script(
195 'visualizer-chart-builder',
196 'document.body.insertAdjacentHTML("beforeend","<div id=\"viz-chart-builder-root\"></div>");',
197 'before'
198 );
199 }
200 }
201 /**
202 * Hook server side rendering into render callback
203 */
204 public function register_block_type() {
205 $asset_path = VISUALIZER_ABSPATH . '/classes/Visualizer/Gutenberg/build/index.asset.php';
206 $version = $this->version;
207 if ( file_exists( $asset_path ) ) {
208 // @phpstan-ignore-next-line
209 $asset = require $asset_path;
210 $version = isset( $asset['version'] ) ? $asset['version'] : $version;
211 }
212 if ( ! wp_style_is( 'visualizer-datatables', 'registered' ) ) {
213 wp_register_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION );
214 }
215 if ( ! wp_style_is( 'visualizer-gutenberg-block', 'registered' ) ) {
216 wp_register_style( 'visualizer-gutenberg-block', VISUALIZER_ABSURL . 'classes/Visualizer/Gutenberg/build/style-index.css', array( 'visualizer-datatables' ), $version );
217 }
218 register_block_type(
219 'visualizer/chart', array(
220 // The editor_style registration is what gets the stylesheet into the
221 // iframed editor canvas; styles enqueued via enqueue_block_editor_assets
222 // only reach the parent document.
223 'editor_style' => 'visualizer-gutenberg-block',
224 'render_callback' => array( $this, 'gutenberg_block_callback' ),
225 'attributes' => array(
226 'id' => array(
227 'type' => 'number',
228 ),
229 'lazy' => array(
230 'type' => 'string',
231 ),
232 ),
233 )
234 );
235 }
236
237 /**
238 * Gutenberg Block Callback Function
239 */
240 public function gutenberg_block_callback( $atts ) {
241 // no id, no fun.
242 if ( ! isset( $atts['id'] ) ) {
243 return '';
244 }
245
246 $atts = shortcode_atts(
247 array(
248 'id' => false,
249 'lazy' => apply_filters( 'visualizer_lazy_by_default', false, $atts['id'] ),
250 // we are deliberating excluding the class attribute from here
251 // as this will be handled by the custom class in Gutenberg
252 ),
253 $atts
254 );
255
256 // no id, no fun.
257 if ( ! $atts['id'] ) {
258 return '';
259 }
260
261 if ( $atts['lazy'] === '-1' || $atts['lazy'] === false ) {
262 $atts['lazy'] = 'no';
263 }
264
265 // we don't want the chart in the editor lazy-loading.
266 if ( is_admin() ) {
267 unset( $atts['lazy'] );
268 }
269
270 $shortcode = '[visualizer';
271 foreach ( $atts as $name => $value ) {
272 $shortcode .= sprintf( ' %s="%s"', $name, $value );
273 }
274 $shortcode .= ']';
275
276 return $shortcode;
277 }
278
279 /**
280 * Hook server side rendering into render callback
281 */
282 public function register_rest_endpoints() {
283 register_rest_field(
284 'visualizer',
285 'chart_data',
286 array(
287 'get_callback' => array( $this, 'get_visualizer_data' ),
288 )
289 );
290 }
291
292 /**
293 * Get Post Meta Fields
294 */
295 public function get_visualizer_data( $post ) {
296 if ( ! Visualizer_Module::can_edit_chart( $post['id'] ) ) {
297 return false;
298 }
299
300 $data = array();
301 $post_id = $post['id'];
302
303 $data['visualizer-chart-type'] = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_TYPE, true );
304
305 $library = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
306 $data['visualizer-chart-library'] = $library;
307 if ( 'd3' === $library ) {
308 $data['visualizer-d3-code'] = get_post_meta( $post_id, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
309 }
310
311 $data['visualizer-source'] = get_post_meta( $post_id, Visualizer_Plugin::CF_SOURCE, true );
312
313 $data['visualizer-default-data'] = get_post_meta( $post_id, Visualizer_Plugin::CF_DEFAULT_DATA, true );
314
315 // faetch and update settings
316 $data['visualizer-settings'] = get_post_meta( $post_id, Visualizer_Plugin::CF_SETTINGS, true );
317 if ( ! is_array( $data['visualizer-settings'] ) ) {
318 $data['visualizer-settings'] = array();
319 }
320 if ( empty( $data['visualizer-settings']['pagination'] ) ) {
321 $data['visualizer-settings']['pageSize'] = '';
322 }
323 // handle series filter hooks
324 $data['visualizer-series'] = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $post_id, Visualizer_Plugin::CF_SERIES, true ), $post_id, $data['visualizer-chart-type'] );
325
326 // handle settings filter hooks
327 $data['visualizer-settings'] = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $data['visualizer-settings'], $post_id, $data['visualizer-chart-type'] );
328
329 // handle data filter hooks
330 $data['visualizer-data'] = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_DATA, Visualizer_Module::decode_content( html_entity_decode( get_post_field( 'post_content', $post_id, 'raw' ) ) ), $post_id, $data['visualizer-chart-type'] );
331
332 // we are going to format only for tabular charts, because we are not sure of the effect on others.
333 // this is to solve the case where boolean data shows up as all-ticks on gutenberg.
334 if ( in_array( $data['visualizer-chart-type'], array( 'tabular' ), true ) ) {
335 $data['visualizer-data'] = $this->format_chart_data( $data['visualizer-data'], $data['visualizer-series'] );
336 }
337
338 if ( ! isset( $data['visualizer-settings']['hAxis']['format'] ) ) {
339 $data['visualizer-settings']['hAxis']['format'] = '';
340 }
341
342 $data['visualizer-data-exploded'] = '';
343 // handle annotations for google charts
344 if ( 'GoogleCharts' === $library ) {
345 // this will contain the data of both axis.
346 $settings = $data['visualizer-settings'];
347 // this will contain data only of Y axis.
348 $series = $data['visualizer-series'];
349 $annotations = array();
350 if ( isset( $settings['series'] ) ) {
351 foreach ( $settings['series'] as $index => $serie ) {
352 // skip X axis data.
353 if ( $index === 0 ) {
354 continue;
355 }
356 if ( ! empty( $serie['role'] ) ) {
357 // this series is some kind of annotation, so let's collect its index.
358 // the index will be +1 because the X axis value is index 0, which is being ignored.
359 $annotations[ 'role' . ( intval( $index ) + 1 ) ] = $serie['role'];
360
361 if ( isset( $data['visualizer-series'][ intval( $index ) + 1 ] ) ) {
362 $data['visualizer-series'][ intval( $index ) + 1 ]['role'] = $serie['role'];
363 }
364 }
365 }
366 }
367 if ( ! empty( $annotations ) ) {
368 $exploded_data = array();
369 $series_names = array();
370 foreach ( $series as $index => $serie ) {
371 // skip X axis data.
372 if ( $index === 0 ) {
373 continue;
374 }
375 if ( array_key_exists( 'role' . $index, $annotations ) ) {
376 $series_names[] = (object) array( 'role' => $annotations[ 'role' . $index ], 'type' => $serie['type'] );
377 } else {
378 $series_names[] = $serie['label'];
379 }
380 }
381 $exploded_data[] = $series_names;
382
383 foreach ( $data['visualizer-data'] as $datum ) {
384 // skip X axis data.
385 unset( $datum[0] );
386 $exploded_data[] = $datum;
387 }
388 $data['visualizer-data-exploded'] = array( $exploded_data );
389 }
390 }
391
392 $import = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_URL, true );
393
394 $schedule = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_SCHEDULE, true );
395
396 $db_schedule = get_post_meta( $post_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
397
398 $db_query = get_post_meta( $post_id, Visualizer_Plugin::CF_DB_QUERY, true );
399
400 $json_url = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_URL, true );
401
402 $json_headers = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_HEADERS, true );
403
404 $json_schedule = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_SCHEDULE, true );
405
406 $json_root = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_ROOT, true );
407
408 $json_paging = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_PAGING, true );
409
410 if ( ! empty( $import ) && $schedule >= 0 ) {
411 $data['visualizer-chart-url'] = $import;
412 $data['visualizer-chart-schedule'] = $schedule;
413 }
414
415 if ( ! empty( $db_schedule ) && ! empty( $db_query ) ) {
416 $data['visualizer-db-schedule'] = $db_schedule;
417 $data['visualizer-db-query'] = $db_query;
418 }
419
420 if ( ! empty( $json_url ) ) {
421 $data['visualizer-json-schedule'] = $json_schedule;
422 $data['visualizer-json-url'] = $json_url;
423 $data['visualizer-json-headers'] = $json_headers;
424 $data['visualizer-json-root'] = $json_root;
425
426 if ( Visualizer_Module::is_pro() && ! empty( $json_paging ) ) {
427 $data['visualizer-json-paging'] = $json_paging;
428 }
429 }
430
431 if ( Visualizer_Module::is_pro() ) {
432 $permissions = get_post_meta( $post_id, Visualizer_Pro::CF_PERMISSIONS, true );
433
434 if ( empty( $permissions ) ) {
435 $permissions = array(
436 'permissions' => array(
437 'read' => 'all',
438 'edit' => 'roles',
439 'edit-specific' => array( 'administrator' ),
440 ),
441 );
442 }
443
444 $data['visualizer-permissions'] = $permissions;
445 }
446
447 return $data;
448 }
449
450 /**
451 * Format chart data.
452 *
453 * Note: No matter how tempted, don't use the similar method from Visualizer_Source. That works on a different structure.
454 */
455 public function format_chart_data( $data, $series ) {
456 foreach ( $series as $i => $row ) {
457 // if no value exists for the series, then add null
458 if ( ! isset( $series[ $i ] ) ) {
459 $series[ $i ] = null;
460 }
461
462 if ( is_null( $series[ $i ] ) ) {
463 continue;
464 }
465
466 switch ( $row['type'] ) {
467 case 'number':
468 foreach ( $data as $o => $col ) {
469 $data[ $o ][ $i ] = ( is_numeric( $col[ $i ] ) ) ? floatval( $col[ $i ] ) : ( is_numeric( str_replace( ',', '', $col[ $i ] ) ) ? floatval( str_replace( ',', '', $col[ $i ] ) ) : null );
470 }
471 break;
472 case 'boolean':
473 foreach ( $data as $o => $col ) {
474 $data[ $o ][ $i ] = ! empty( $col[ $i ] ) ? filter_var( $col[ $i ], FILTER_VALIDATE_BOOLEAN ) : false;
475 }
476 break;
477 case 'timeofday':
478 foreach ( $data as $o => $col ) {
479 $date = new DateTime( '1984-03-16T' . $col[ $i ] );
480 if ( $date ) {
481 $data[ $o ][ $i ] = array(
482 intval( $date->format( 'H' ) ),
483 intval( $date->format( 'i' ) ),
484 intval( $date->format( 's' ) ),
485 0,
486 );
487 }
488 }
489 break;
490 case 'string':
491 foreach ( $data as $o => $col ) {
492 $data[ $o ][ $i ] = $this->toUTF8( $col[ $i ] );
493 }
494 break;
495 }
496 }
497
498 return $data;
499 }
500
501 /**
502 * Use toUTF8 function
503 */
504 public function toUTF8( $datum ) {
505 if ( ! function_exists( 'mb_detect_encoding' ) || mb_detect_encoding( $datum ) !== 'ASCII' ) {
506 $datum = \ForceUTF8\Encoding::toUTF8( $datum );
507 }
508 return $datum;
509 }
510
511 /**
512 * Filter Rest Query
513 */
514 public function add_rest_query_vars( $args, \WP_REST_Request $request ) {
515 if ( isset( $request['meta_key'] ) && isset( $request['meta_value'] ) ) {
516 $args['meta_query'] = array(
517 'relation' => 'OR',
518 array(
519 'key' => $request->get_param( 'meta_key' ),
520 'value' => $request->get_param( 'meta_value' ),
521 'compare' => '!=',
522 ),
523 array(
524 'key' => $request->get_param( 'meta_key' ),
525 'value' => $request->get_param( 'meta_value' ),
526 'compare' => 'NOT EXISTS',
527 ),
528 );
529 }
530 return $args;
531 }
532 }
533