PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.6
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.6
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 / Gutenberg / Block.php

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

513 lines 16.8 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 register_block_type(
206 'visualizer/chart', array(
207 'render_callback' => array( $this, 'gutenberg_block_callback' ),
208 'attributes' => array(
209 'id' => array(
210 'type' => 'number',
211 ),
212 'lazy' => array(
213 'type' => 'string',
214 ),
215 ),
216 )
217 );
218 }
219
220 /**
221 * Gutenberg Block Callback Function
222 */
223 public function gutenberg_block_callback( $atts ) {
224 // no id, no fun.
225 if ( ! isset( $atts['id'] ) ) {
226 return '';
227 }
228
229 $atts = shortcode_atts(
230 array(
231 'id' => false,
232 'lazy' => apply_filters( 'visualizer_lazy_by_default', false, $atts['id'] ),
233 // we are deliberating excluding the class attribute from here
234 // as this will be handled by the custom class in Gutenberg
235 ),
236 $atts
237 );
238
239 // no id, no fun.
240 if ( ! $atts['id'] ) {
241 return '';
242 }
243
244 if ( $atts['lazy'] === '-1' || $atts['lazy'] === false ) {
245 $atts['lazy'] = 'no';
246 }
247
248 // we don't want the chart in the editor lazy-loading.
249 if ( is_admin() ) {
250 unset( $atts['lazy'] );
251 }
252
253 $shortcode = '[visualizer';
254 foreach ( $atts as $name => $value ) {
255 $shortcode .= sprintf( ' %s="%s"', $name, $value );
256 }
257 $shortcode .= ']';
258
259 return $shortcode;
260 }
261
262 /**
263 * Hook server side rendering into render callback
264 */
265 public function register_rest_endpoints() {
266 register_rest_field(
267 'visualizer',
268 'chart_data',
269 array(
270 'get_callback' => array( $this, 'get_visualizer_data' ),
271 )
272 );
273 }
274
275 /**
276 * Get Post Meta Fields
277 */
278 public function get_visualizer_data( $post ) {
279 if ( ! current_user_can( 'edit_posts' ) ) {
280 return false;
281 }
282
283 $data = array();
284 $post_id = $post['id'];
285
286 $data['visualizer-chart-type'] = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_TYPE, true );
287
288 $library = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
289 $data['visualizer-chart-library'] = $library;
290 if ( 'd3' === $library ) {
291 $data['visualizer-d3-code'] = get_post_meta( $post_id, Visualizer_Module_AIBuilder::CF_D3_CODE, true );
292 }
293
294 $data['visualizer-source'] = get_post_meta( $post_id, Visualizer_Plugin::CF_SOURCE, true );
295
296 $data['visualizer-default-data'] = get_post_meta( $post_id, Visualizer_Plugin::CF_DEFAULT_DATA, true );
297
298 // faetch and update settings
299 $data['visualizer-settings'] = get_post_meta( $post_id, Visualizer_Plugin::CF_SETTINGS, true );
300 if ( empty( $data['visualizer-settings']['pagination'] ) ) {
301 $data['visualizer-settings']['pageSize'] = '';
302 }
303 // handle series filter hooks
304 $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'] );
305
306 // handle settings filter hooks
307 $data['visualizer-settings'] = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, $data['visualizer-settings'], $post_id, $data['visualizer-chart-type'] );
308
309 // handle data filter hooks
310 $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'] );
311
312 // we are going to format only for tabular charts, because we are not sure of the effect on others.
313 // this is to solve the case where boolean data shows up as all-ticks on gutenberg.
314 if ( in_array( $data['visualizer-chart-type'], array( 'tabular' ), true ) ) {
315 $data['visualizer-data'] = $this->format_chart_data( $data['visualizer-data'], $data['visualizer-series'] );
316 }
317
318 if ( ! isset( $data['visualizer-settings']['hAxis']['format'] ) ) {
319 $data['visualizer-settings']['hAxis']['format'] = '';
320 }
321
322 $data['visualizer-data-exploded'] = '';
323 // handle annotations for google charts
324 if ( 'GoogleCharts' === $library ) {
325 // this will contain the data of both axis.
326 $settings = $data['visualizer-settings'];
327 // this will contain data only of Y axis.
328 $series = $data['visualizer-series'];
329 $annotations = array();
330 if ( isset( $settings['series'] ) ) {
331 foreach ( $settings['series'] as $index => $serie ) {
332 // skip X axis data.
333 if ( $index === 0 ) {
334 continue;
335 }
336 if ( ! empty( $serie['role'] ) ) {
337 // this series is some kind of annotation, so let's collect its index.
338 // the index will be +1 because the X axis value is index 0, which is being ignored.
339 $annotations[ 'role' . ( intval( $index ) + 1 ) ] = $serie['role'];
340
341 if ( isset( $data['visualizer-series'][ intval( $index ) + 1 ] ) ) {
342 $data['visualizer-series'][ intval( $index ) + 1 ]['role'] = $serie['role'];
343 }
344 }
345 }
346 }
347 if ( ! empty( $annotations ) ) {
348 $exploded_data = array();
349 $series_names = array();
350 foreach ( $series as $index => $serie ) {
351 // skip X axis data.
352 if ( $index === 0 ) {
353 continue;
354 }
355 if ( array_key_exists( 'role' . $index, $annotations ) ) {
356 $series_names[] = (object) array( 'role' => $annotations[ 'role' . $index ], 'type' => $serie['type'] );
357 } else {
358 $series_names[] = $serie['label'];
359 }
360 }
361 $exploded_data[] = $series_names;
362
363 foreach ( $data['visualizer-data'] as $datum ) {
364 // skip X axis data.
365 unset( $datum[0] );
366 $exploded_data[] = $datum;
367 }
368 $data['visualizer-data-exploded'] = array( $exploded_data );
369 }
370 }
371
372 $import = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_URL, true );
373
374 $schedule = get_post_meta( $post_id, Visualizer_Plugin::CF_CHART_SCHEDULE, true );
375
376 $db_schedule = get_post_meta( $post_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
377
378 $db_query = get_post_meta( $post_id, Visualizer_Plugin::CF_DB_QUERY, true );
379
380 $json_url = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_URL, true );
381
382 $json_headers = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_HEADERS, true );
383
384 $json_schedule = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_SCHEDULE, true );
385
386 $json_root = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_ROOT, true );
387
388 $json_paging = get_post_meta( $post_id, Visualizer_Plugin::CF_JSON_PAGING, true );
389
390 if ( ! empty( $import ) && $schedule >= 0 ) {
391 $data['visualizer-chart-url'] = $import;
392 $data['visualizer-chart-schedule'] = $schedule;
393 }
394
395 if ( ! empty( $db_schedule ) && ! empty( $db_query ) ) {
396 $data['visualizer-db-schedule'] = $db_schedule;
397 $data['visualizer-db-query'] = $db_query;
398 }
399
400 if ( ! empty( $json_url ) ) {
401 $data['visualizer-json-schedule'] = $json_schedule;
402 $data['visualizer-json-url'] = $json_url;
403 $data['visualizer-json-headers'] = $json_headers;
404 $data['visualizer-json-root'] = $json_root;
405
406 if ( Visualizer_Module::is_pro() && ! empty( $json_paging ) ) {
407 $data['visualizer-json-paging'] = $json_paging;
408 }
409 }
410
411 if ( Visualizer_Module::is_pro() ) {
412 $permissions = get_post_meta( $post_id, Visualizer_Pro::CF_PERMISSIONS, true );
413
414 if ( empty( $permissions ) ) {
415 $permissions = array(
416 'permissions' => array(
417 'read' => 'all',
418 'edit' => 'roles',
419 'edit-specific' => array( 'administrator' ),
420 ),
421 );
422 }
423
424 $data['visualizer-permissions'] = $permissions;
425 }
426
427 return $data;
428 }
429
430 /**
431 * Format chart data.
432 *
433 * Note: No matter how tempted, don't use the similar method from Visualizer_Source. That works on a different structure.
434 */
435 public function format_chart_data( $data, $series ) {
436 foreach ( $series as $i => $row ) {
437 // if no value exists for the series, then add null
438 if ( ! isset( $series[ $i ] ) ) {
439 $series[ $i ] = null;
440 }
441
442 if ( is_null( $series[ $i ] ) ) {
443 continue;
444 }
445
446 switch ( $row['type'] ) {
447 case 'number':
448 foreach ( $data as $o => $col ) {
449 $data[ $o ][ $i ] = ( is_numeric( $col[ $i ] ) ) ? floatval( $col[ $i ] ) : ( is_numeric( str_replace( ',', '', $col[ $i ] ) ) ? floatval( str_replace( ',', '', $col[ $i ] ) ) : null );
450 }
451 break;
452 case 'boolean':
453 foreach ( $data as $o => $col ) {
454 $data[ $o ][ $i ] = ! empty( $col[ $i ] ) ? filter_var( $col[ $i ], FILTER_VALIDATE_BOOLEAN ) : false;
455 }
456 break;
457 case 'timeofday':
458 foreach ( $data as $o => $col ) {
459 $date = new DateTime( '1984-03-16T' . $col[ $i ] );
460 if ( $date ) {
461 $data[ $o ][ $i ] = array(
462 intval( $date->format( 'H' ) ),
463 intval( $date->format( 'i' ) ),
464 intval( $date->format( 's' ) ),
465 0,
466 );
467 }
468 }
469 break;
470 case 'string':
471 foreach ( $data as $o => $col ) {
472 $data[ $o ][ $i ] = $this->toUTF8( $col[ $i ] );
473 }
474 break;
475 }
476 }
477
478 return $data;
479 }
480
481 /**
482 * Use toUTF8 function
483 */
484 public function toUTF8( $datum ) {
485 if ( ! function_exists( 'mb_detect_encoding' ) || mb_detect_encoding( $datum ) !== 'ASCII' ) {
486 $datum = \ForceUTF8\Encoding::toUTF8( $datum );
487 }
488 return $datum;
489 }
490
491 /**
492 * Filter Rest Query
493 */
494 public function add_rest_query_vars( $args, \WP_REST_Request $request ) {
495 if ( isset( $request['meta_key'] ) && isset( $request['meta_value'] ) ) {
496 $args['meta_query'] = array(
497 'relation' => 'OR',
498 array(
499 'key' => $request->get_param( 'meta_key' ),
500 'value' => $request->get_param( 'meta_value' ),
501 'compare' => '!=',
502 ),
503 array(
504 'key' => $request->get_param( 'meta_key' ),
505 'value' => $request->get_param( 'meta_value' ),
506 'compare' => 'NOT EXISTS',
507 ),
508 );
509 }
510 return $args;
511 }
512 }
513