PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.3.4
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.3.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.php

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

644 lines 19.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 * Base class for all modules. Implements routine methods required by all modules.
24 *
25 * @category Visualizer
26 * @package Module
27 *
28 * @since 1.0.0
29 */
30 class Visualizer_Module {
31
32 /**
33 * The instance of wpdb class.
34 *
35 * @since 1.0.0
36 *
37 * @access protected
38 * @var wpdb
39 */
40 protected $_wpdb = null;
41
42 /**
43 * The plugin instance.
44 *
45 * @since 1.0.0
46 *
47 * @access protected
48 * @var Visualizer_Plugin
49 */
50 protected $_plugin = null;
51
52 /**
53 * Constructor.
54 *
55 * @since 1.0.0
56 * @global wpdb $wpdb Current database connection.
57 *
58 * @access public
59 * @param Visualizer_Plugin $plugin The instance of the plugin.
60 */
61 public function __construct( Visualizer_Plugin $plugin ) {
62 global $wpdb;
63
64 $this->_wpdb = $wpdb;
65 $this->_plugin = $plugin;
66
67 $this->_addFilter( Visualizer_Plugin::FILTER_UNDO_REVISIONS, 'undoRevisions', 10, 2 );
68 $this->_addFilter( Visualizer_Plugin::FILTER_HANDLE_REVISIONS, 'handleExistingRevisions', 10, 2 );
69 $this->_addFilter( Visualizer_Plugin::FILTER_GET_CHART_DATA_AS, 'getDataAs', 10, 3 );
70
71 }
72
73 /**
74 * Registers an action hook.
75 *
76 * @since 1.0.0
77 * @uses add_action() To register action hook.
78 *
79 * @access protected
80 * @param string $tag The name of the action to which the $method is hooked.
81 * @param string $method The name of the method to be called.
82 * @param bool $methodClass The root of the method.
83 * @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action.
84 * @param int $accepted_args optional. The number of arguments the function accept (default 1).
85 * @return Visualizer_Module
86 */
87 protected function _addAction( $tag, $method, $methodClass = null, $priority = 10, $accepted_args = 1 ) {
88 add_action( $tag, array( $methodClass ? $methodClass : $this, $method ), $priority, $accepted_args );
89 return $this;
90 }
91
92 /**
93 * Registers AJAX action hook.
94 *
95 * @since 1.0.0
96 *
97 * @access public
98 * @param string $tag The name of the AJAX action to which the $method is hooked.
99 * @param string $method Optional. The name of the method to be called. If the name of the method is not provided, tag name will be used as method name.
100 * @param bool $methodClass The root of the method.
101 * @param boolean $private Optional. Determines if we should register hook for logged in users.
102 * @param boolean $public Optional. Determines if we should register hook for not logged in users.
103 * @return Visualizer_Module
104 */
105 protected function _addAjaxAction( $tag, $method = '', $methodClass = null, $private = true, $public = false ) {
106 if ( $private ) {
107 $this->_addAction( 'wp_ajax_' . $tag, $method, $methodClass );
108 }
109
110 if ( $public ) {
111 $this->_addAction( 'wp_ajax_nopriv_' . $tag, $method, $methodClass );
112 }
113
114 return $this;
115 }
116
117 /**
118 * Registers a filter hook.
119 *
120 * @since 1.0.0
121 * @uses add_filter() To register filter hook.
122 *
123 * @access protected
124 * @param string $tag The name of the filter to hook the $method to.
125 * @param type $method The name of the method to be called when the filter is applied.
126 * @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action.
127 * @param int $accepted_args optional. The number of arguments the function accept (default 1).
128 * @return Visualizer_Module
129 */
130 protected function _addFilter( $tag, $method, $priority = 10, $accepted_args = 1 ) {
131 add_filter( $tag, array( $this, $method ), $priority, $accepted_args );
132 return $this;
133 }
134
135 /**
136 * Registers a hook for shortcode tag.
137 *
138 * @since 1.0.0
139 * @uses add_shortcode() To register shortcode hook.
140 *
141 * @access protected
142 * @param string $tag Shortcode tag to be searched in post content.
143 * @param string $method Hook to run when shortcode is found.
144 * @return Visualizer_Module
145 */
146 protected function _addShortcode( $tag, $method ) {
147 add_shortcode( $tag, array( $this, $method ) );
148 return $this;
149 }
150
151 /**
152 * A wrapper around the actual function _getDataAs. This function is invoked as a filter.
153 *
154 * @since 3.2.0
155 */
156 public function getDataAs( $final, $chart_id, $type ) {
157 return $this->_getDataAs( $chart_id, $type );
158 }
159
160 /**
161 * Extracts the data for a chart and prepares it for the given type.
162 *
163 * @access public
164 * @param int $chart_id The chart id.
165 * @param string $type The exported type.
166 */
167 public function _getDataAs( $chart_id, $type ) {
168 $final = null;
169 $success = false;
170 if ( $chart_id ) {
171 $chart = get_post( $chart_id );
172 $success = $chart && $chart->post_type === Visualizer_Plugin::CPT_VISUALIZER;
173 }
174 if ( $success ) {
175 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
176 $rows = array();
177 $series = get_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, true );
178 $data = unserialize( $chart->post_content );
179 if ( ! empty( $series ) ) {
180 $row = array();
181 foreach ( $series as $array ) {
182 $row[] = $array['label'];
183 }
184 $rows[] = $row;
185 $row = array();
186 foreach ( $series as $array ) {
187 $row[] = $array['type'];
188 }
189 $rows[] = $row;
190 }
191 if ( ! empty( $data ) ) {
192 foreach ( $data as $array ) {
193 // ignore strings
194 if ( ! is_array( $array ) ) {
195 continue;
196 }
197 // if this is an array of arrays...
198 if ( is_array( $array[0] ) ) {
199 foreach ( $array as $arr ) {
200 $rows[] = $arr;
201 }
202 } else {
203 // just an array
204 $rows[] = $array;
205 }
206 }
207 }
208
209 $title = 'visualizer#' . $chart_id;
210 if ( ! empty( $settings['title'] ) ) {
211 $title = $settings['title'];
212 }
213 // for ChartJS, title is an array.
214 if ( is_array( $title ) && isset( $title['text'] ) ) {
215 $title = $title['text'];
216 }
217 if ( empty( $title ) ) {
218 $title = 'visualizer#' . $chart_id;
219 }
220
221 $filename = $title;
222
223 switch ( $type ) {
224 case 'csv':
225 $final = $this->_getCSV( $rows, $filename );
226 break;
227 case 'xls':
228 $final = $this->_getExcel( $rows, $filename );
229 break;
230 case 'print':
231 $final = $this->_getHTML( $rows );
232 break;
233 }
234 }
235 return $final;
236 }
237
238 /**
239 * Prepares a CSV.
240 *
241 * @access private
242 * @param array $rows The array of data.
243 * @param string $filename The name of the file to use.
244 */
245 private function _getCSV( $rows, $filename ) {
246 $filename .= '.csv';
247
248 $bom = chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF );
249 $fp = tmpfile();
250 // support for MS Excel
251 fprintf( $fp, $bom );
252 foreach ( $rows as $row ) {
253 fputcsv( $fp, $row );
254 }
255 rewind( $fp );
256 $csv = '';
257 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
258 while ( ( $array = fgetcsv( $fp ) ) !== false ) {
259 if ( strlen( $csv ) > 0 ) {
260 $csv .= PHP_EOL;
261 }
262 $csv .= implode( ',', $array );
263 }
264 fclose( $fp );
265
266 return array(
267 'csv' => $csv,
268 'name' => $filename,
269 'string' => str_replace( $bom, '', $csv ),
270 );
271 }
272
273 /**
274 * Prepares an Excel file.
275 *
276 * @access private
277 * @param array $rows The array of data.
278 * @param string $filename The name of the file to use.
279 */
280 private function _getExcel( $rows, $filename ) {
281 // PHPExcel did not like sheet names longer than 31 characters and we will assume the same with PhpSpreadsheet
282 $chart = substr( $filename, 0, 30 );
283 $filename .= '.xlsx';
284
285 $vendor_file = VISUALIZER_ABSPATH . '/vendor/autoload.php';
286 if ( is_readable( $vendor_file ) ) {
287 include_once( $vendor_file );
288 }
289
290 $xlsData = '';
291 if ( class_exists( 'PhpOffice\PhpSpreadsheet\Spreadsheet' ) ) {
292 $doc = new PhpOffice\PhpSpreadsheet\Spreadsheet();
293 $doc->getActiveSheet()->fromArray( $rows, null, 'A1' );
294 $doc->getActiveSheet()->setTitle( sanitize_title( $chart ) );
295 $doc = apply_filters( 'visualizer_excel_doc', $doc );
296 $writer = PhpOffice\PhpSpreadsheet\IOFactory::createWriter( $doc, 'Xlsx' );
297 ob_start();
298 $writer->save( 'php://output' );
299 $xlsData = ob_get_contents();
300 ob_end_clean();
301 } else {
302 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Class PhpOffice\PhpSpreadsheet\Spreadsheet does not exist!', 'error', __FILE__, __LINE__ );
303 error_log( 'Class PhpOffice\PhpSpreadsheet\Spreadsheet does not exist!' );
304 }
305 return array(
306 'csv' => 'data:application/vnd.ms-excel;base64,' . base64_encode( $xlsData ),
307 'name' => $filename,
308 'raw' => base64_encode( $xlsData ),
309 );
310 }
311
312 /**
313 * Prepares an HTML table.
314 *
315 * @access private
316 * @param array $rows The array of data.
317 */
318 private function _getHTML( $rows ) {
319 $css = '
320 table.visualizer-print {
321 border-collapse: collapse;
322 }
323 table.visualizer-print, table.visualizer-print th, table.visualizer-print td {
324 border: 1px solid #000;
325 }
326 ';
327 $html = '';
328 $html .= '
329 <html>
330 <head>
331 <style>
332 ' . apply_filters( 'visualizer_print_css', $css ) . '
333 </style>
334 </head>
335 <body>';
336
337 $table = '<table class="visualizer-print">';
338 $index = 0;
339 foreach ( $rows as $row ) {
340 // skip the data type row.
341 if ( 1 === $index ) {
342 $index++;
343 continue;
344 }
345
346 $table .= '<tr>';
347 foreach ( $row as $col ) {
348 if ( $index === 0 ) {
349 $table .= '<th>' . $col . '</th>';
350 } else {
351 $table .= '<td>' . $col . '</td>';
352 }
353 }
354 $table .= '</tr>';
355 $index++;
356 }
357 $table .= '</table>';
358
359 $html .= apply_filters( 'visualizer_print_table', $table ) . '
360 </body>
361 </html>';
362 return array(
363 'csv' => $html,
364 );
365 }
366
367 /**
368 * Undo revisions for the chart, and if necessary, restore the earliest version.
369 *
370 * @return bool If any revisions were found.
371 */
372 public final function undoRevisions( $chart_id, $restore = false ) {
373 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'undoRevisions for %d with%s restore', $chart_id, ( $restore ? '' : 'out' ) ), 'debug', __FILE__, __LINE__ );
374
375 $revisions = wp_get_post_revisions( $chart_id, array( 'order' => 'ASC' ) );
376 if ( count( $revisions ) > 1 ) {
377 $revision_ids = array_keys( $revisions );
378
379 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'found %d revisions = %s', count( $revisions ), print_r( $revision_ids, true ) ), 'debug', __FILE__, __LINE__ );
380
381 // when we restore, a new revision is likely to be created. so, let's disable revisions for the time being.
382 add_filter( 'wp_revisions_to_keep', '__return_false' );
383
384 if ( $restore ) {
385 // restore to the oldest one i.e. the first one.
386 wp_restore_post_revision( array_shift( $revision_ids ) );
387 }
388
389 // delete all revisions.
390 foreach ( $revision_ids as $id ) {
391 wp_delete_post_revision( $id );
392 }
393
394 return true;
395 }
396 return false;
397 }
398
399 /**
400 * If existing revisions exist for the chart, restore the earliest version and then create a new revision to initiate editing.
401 */
402 public final function handleExistingRevisions( $chart_id, $chart ) {
403 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'handleExistingRevisions for %d', $chart_id ), 'debug', __FILE__, __LINE__ );
404
405 // undo revisions.
406 $revisions_found = $this->undoRevisions( $chart_id, true );
407
408 // create revision for the edit action.
409 wp_save_post_revision( $chart_id );
410
411 if ( $revisions_found ) {
412 // fetch chart data again in case it was updated by an earlier revision.
413 $chart = get_post( $chart_id );
414 }
415 return $chart;
416 }
417
418 /**
419 * Returns the language of the locale.
420 *
421 * @access protected
422 */
423 protected function get_language() {
424 $locale = get_locale();
425 if ( empty( $locale ) ) {
426 return '';
427 }
428 $array = explode( '_', $locale );
429 if ( count( $array ) < 2 ) {
430 return '';
431 }
432 return reset( $array );
433 }
434
435 /**
436 * Gets/creates the JS where user-specific customizations can be/have been added.
437 */
438 protected function get_user_customization_js() {
439 // use this as the JS file in case we are not able to create the file in uploads.
440 $default = VISUALIZER_ABSURL . 'js/customization.js';
441
442 $uploads = wp_get_upload_dir();
443 $specific = $uploads['baseurl'] . '/visualizer/customization.js';
444
445 // for testing on user sites (before we send them the correctly customized file).
446 if ( VISUALIZER_TEST_JS_CUSTOMIZATION ) {
447 return $default;
448 }
449
450 require_once( ABSPATH . 'wp-admin/includes/file.php' );
451 WP_Filesystem();
452 global $wp_filesystem;
453
454 $multisite_arg = '/';
455 if ( is_multisite() && ! is_main_site() ) {
456 $multisite_arg = '/sites/' . get_current_blog_id() . '/';
457 }
458
459 $dir = $wp_filesystem->wp_content_dir() . 'uploads' . $multisite_arg . 'visualizer';
460 $file = $wp_filesystem->wp_content_dir() . 'uploads' . $multisite_arg . 'visualizer/customization.js';
461
462 if ( $wp_filesystem->is_readable( $file ) ) {
463 return $specific;
464 }
465
466 if ( $wp_filesystem->exists( $file ) && ! $wp_filesystem->is_readable( $file ) ) {
467 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to read file %s', $file ), 'error', __FILE__, __LINE__ );
468 return $default;
469 }
470
471 if ( ! $wp_filesystem->exists( $dir ) ) {
472 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
473 if ( ( $done = $wp_filesystem->mkdir( $dir ) ) === false ) {
474 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to create directory %s', $dir ), 'error', __FILE__, __LINE__ );
475 return $default;
476 }
477 }
478
479 // if file does not exist, copy.
480 if ( ! $wp_filesystem->exists( $file ) ) {
481 $src = str_replace( ABSPATH, $wp_filesystem->abspath(), VISUALIZER_ABSPATH . '/js/customization.js' );
482 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
483 if ( ( $done = $wp_filesystem->copy( $src, $file ) ) === false ) {
484 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to copy file %s to %s', $src, $file ), 'error', __FILE__, __LINE__ );
485 return $default;
486 }
487 }
488
489 return $specific;
490 }
491
492 /**
493 * Load the class for the given chart's chart type so that its assets can be loaded.
494 */
495 protected function load_chart_type( $chart_id ) {
496 $name = $this->load_chart_class_name( $chart_id );
497 $class = null;
498 if ( class_exists( $name ) || true === apply_filters( 'visualizer_load_chart', false, $name ) ) {
499 $class = new $name;
500 }
501
502 if ( is_null( $class ) && Visualizer_Module::is_pro() ) {
503 // lets see if this type exists in pro. New Lite(3.1.0+) & old Pro(1.8.0-).
504 $type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
505 $class = apply_filters( 'visualizer_pro_chart_type_sidebar', null, array( 'id' => $chart_id, 'type' => $type, 'settings' => get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true ) ) );
506 }
507
508 return is_null( $class ) ? null : $class->getLibrary();
509 }
510
511 /**
512 * Returns the class name for the given chart's chart type.
513 */
514 protected function load_chart_class_name( $chart_id ) {
515 $type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
516 $lib = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
517
518 // backward compatibility.
519 if ( empty( $lib ) ) {
520 $lib = 'GoogleCharts';
521 if ( 'dataTable' === $type ) {
522 $lib = 'DataTable';
523 }
524 }
525 $name = 'Visualizer_Render_Sidebar_Type_' . $lib . '_' . ucwords( $type );
526 return $name;
527 }
528
529 /**
530 * Generates the inline CSS to apply to the chart and adds these classes to the settings.
531 *
532 * @access public
533 * @param int $id The id of the chart.
534 * @param array $settings The settings of the chart.
535 */
536 protected function get_inline_custom_css( $id, $settings ) {
537 $css = '';
538
539 $arguments = array( '', $settings );
540 if ( ! isset( $settings['customcss'] ) ) {
541 return $arguments;
542 }
543
544 $classes = array();
545 $css = '<style type="text/css" name="visualizer-custom-css" id="customcss-' . $id . '">';
546 foreach ( $settings['customcss'] as $name => $element ) {
547 $attributes = array();
548 foreach ( $element as $property => $value ) {
549 $attributes[] = $this->handle_css_property( $property, $value );
550 }
551 $class_name = $id . $name;
552 $properties = implode( ' !important; ', array_filter( $attributes ) );
553 if ( ! empty( $properties ) ) {
554 $css .= '.' . $class_name . ' {' . $properties . ' !important;}';
555 $classes[ $name ] = $class_name;
556 }
557 }
558 $css .= '</style>';
559
560 $settings['cssClassNames'] = $classes;
561
562 $arguments = array( $css, $settings );
563 apply_filters_ref_array( 'visualizer_inline_css', array( &$arguments ) );
564
565 return $arguments;
566 }
567
568 /**
569 * Handles CSS properties that might need special syntax.
570 *
571 * @access private
572 * @param string $property The name of the css property.
573 * @param string $value The value of the css property.
574 */
575 private function handle_css_property( $property, $value ) {
576 if ( empty( $property ) || empty( $value ) ) {
577 return '';
578 }
579
580 switch ( $property ) {
581 case 'transform':
582 $value = 'rotate(' . $value . 'deg)';
583 break;
584 }
585 return $property . ': ' . $value;
586 }
587
588 /**
589 * Determines if charts have been created of the particular chart type.
590 */
591 protected static function hasChartType( $type ) {
592 $args = array(
593 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
594 'fields' => 'ids',
595 'post_status' => 'publish',
596 'meta_query' => array(
597 array(
598 'key' => Visualizer_Plugin::CF_CHART_TYPE,
599 'value' => $type,
600 ),
601 ),
602 );
603
604 $q = new WP_Query( $args );
605 return $q->found_posts > 0;
606 }
607
608 /**
609 * Determines how many charts have been created.
610 */
611 protected static function numberOfCharts() {
612 $args = array(
613 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
614 'fields' => 'ids',
615 'post_status' => 'publish',
616 'posts_per_page' => 300,
617 );
618
619 $q = new WP_Query( $args );
620 return $q->found_posts;
621 }
622
623 /**
624 * Checks if the PRO version is active.
625 *
626 * @since 3.3.0
627 */
628 public static function is_pro() {
629 // versions of pro before 1.9.0 will use the constant VISUALIZER_PRO
630 // versions of pro 1.9.0 onwards will use the filter
631 return apply_filters( 'visualizer_is_pro', VISUALIZER_PRO );
632 }
633
634 /**
635 * Checks if the PRO version is older than a particular version.
636 *
637 * @since 3.3.0
638 */
639 public static function is_pro_older_than( $version ) {
640 return version_compare( VISUALIZER_PRO_VERSION, $version, '<' );
641 }
642
643 }
644