PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.2.1
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.2.1
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.2.1, at classes/Visualizer/Module.php

574 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 // +----------------------------------------------------------------------+
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 $filename = isset( $settings['title'] ) && ! empty( $settings['title'] ) ? $settings['title'] : 'visualizer#' . $chart_id;
210
211 switch ( $type ) {
212 case 'csv':
213 $final = $this->_getCSV( $rows, $filename );
214 break;
215 case 'xls':
216 $final = $this->_getExcel( $rows, $filename );
217 break;
218 case 'print':
219 $final = $this->_getHTML( $rows );
220 break;
221 }
222 }
223 return $final;
224 }
225
226 /**
227 * Prepares a CSV.
228 *
229 * @access private
230 * @param array $rows The array of data.
231 * @param string $filename The name of the file to use.
232 */
233 private function _getCSV( $rows, $filename ) {
234 $filename .= '.csv';
235
236 $bom = chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF );
237 $fp = tmpfile();
238 // support for MS Excel
239 fprintf( $fp, $bom );
240 foreach ( $rows as $row ) {
241 fputcsv( $fp, $row );
242 }
243 rewind( $fp );
244 $csv = '';
245 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
246 while ( ( $array = fgetcsv( $fp ) ) !== false ) {
247 if ( strlen( $csv ) > 0 ) {
248 $csv .= PHP_EOL;
249 }
250 $csv .= implode( ',', $array );
251 }
252 fclose( $fp );
253
254 return array(
255 'csv' => $csv,
256 'name' => $filename,
257 'string' => str_replace( $bom, '', $csv ),
258 );
259 }
260
261 /**
262 * Prepares an Excel file.
263 *
264 * @access private
265 * @param array $rows The array of data.
266 * @param string $filename The name of the file to use.
267 */
268 private function _getExcel( $rows, $filename ) {
269 // PHPExcel did not like sheet names longer than 31 characters and we will assume the same with PhpSpreadsheet
270 $chart = substr( $filename, 0, 30 );
271 $filename .= '.xlsx';
272
273 $vendor_file = VISUALIZER_ABSPATH . '/vendor/autoload.php';
274 if ( is_readable( $vendor_file ) ) {
275 include_once( $vendor_file );
276 }
277
278 $xlsData = '';
279 if ( class_exists( 'PhpOffice\PhpSpreadsheet\Spreadsheet' ) ) {
280 $doc = new PhpOffice\PhpSpreadsheet\Spreadsheet();
281 $doc->getActiveSheet()->fromArray( $rows, null, 'A1' );
282 $doc->getActiveSheet()->setTitle( sanitize_title( $chart ) );
283 $doc = apply_filters( 'visualizer_excel_doc', $doc );
284 $writer = PhpOffice\PhpSpreadsheet\IOFactory::createWriter( $doc, 'Xlsx' );
285 ob_start();
286 $writer->save( 'php://output' );
287 $xlsData = ob_get_contents();
288 ob_end_clean();
289 } else {
290 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Class PhpOffice\PhpSpreadsheet\Spreadsheet does not exist!', 'error', __FILE__, __LINE__ );
291 error_log( 'Class PhpOffice\PhpSpreadsheet\Spreadsheet does not exist!' );
292 }
293 return array(
294 'csv' => 'data:application/vnd.ms-excel;base64,' . base64_encode( $xlsData ),
295 'name' => $filename,
296 'raw' => base64_encode( $xlsData ),
297 );
298 }
299
300 /**
301 * Prepares an HTML table.
302 *
303 * @access private
304 * @param array $rows The array of data.
305 */
306 private function _getHTML( $rows ) {
307 $css = '
308 table.visualizer-print {
309 border-collapse: collapse;
310 }
311 table.visualizer-print, table.visualizer-print th, table.visualizer-print td {
312 border: 1px solid #000;
313 }
314 ';
315 $html = '';
316 $html .= '
317 <html>
318 <head>
319 <style>
320 ' . apply_filters( 'visualizer_print_css', $css ) . '
321 </style>
322 </head>
323 <body>';
324
325 $table = '<table class="visualizer-print">';
326 $index = 0;
327 foreach ( $rows as $row ) {
328 // skip the data type row.
329 if ( 1 === $index ) {
330 $index++;
331 continue;
332 }
333
334 $table .= '<tr>';
335 foreach ( $row as $col ) {
336 if ( $index === 0 ) {
337 $table .= '<th>' . $col . '</th>';
338 } else {
339 $table .= '<td>' . $col . '</td>';
340 }
341 }
342 $table .= '</tr>';
343 $index++;
344 }
345 $table .= '</table>';
346
347 $html .= apply_filters( 'visualizer_print_table', $table ) . '
348 </body>
349 </html>';
350 return array(
351 'csv' => $html,
352 );
353 }
354
355 /**
356 * Undo revisions for the chart, and if necessary, restore the earliest version.
357 *
358 * @return bool If any revisions were found.
359 */
360 public final function undoRevisions( $chart_id, $restore = false ) {
361 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'undoRevisions for %d with%s restore', $chart_id, ( $restore ? '' : 'out' ) ), 'debug', __FILE__, __LINE__ );
362
363 $revisions = wp_get_post_revisions( $chart_id, array( 'order' => 'ASC' ) );
364 if ( count( $revisions ) > 1 ) {
365 $revision_ids = array_keys( $revisions );
366
367 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'found %d revisions = %s', count( $revisions ), print_r( $revision_ids, true ) ), 'debug', __FILE__, __LINE__ );
368
369 // when we restore, a new revision is likely to be created. so, let's disable revisions for the time being.
370 add_filter( 'wp_revisions_to_keep', '__return_false' );
371
372 if ( $restore ) {
373 // restore to the oldest one i.e. the first one.
374 wp_restore_post_revision( array_shift( $revision_ids ) );
375 }
376
377 // delete all revisions.
378 foreach ( $revision_ids as $id ) {
379 wp_delete_post_revision( $id );
380 }
381
382 return true;
383 }
384 return false;
385 }
386
387 /**
388 * If existing revisions exist for the chart, restore the earliest version and then create a new revision to initiate editing.
389 */
390 public final function handleExistingRevisions( $chart_id, $chart ) {
391 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'handleExistingRevisions for %d', $chart_id ), 'debug', __FILE__, __LINE__ );
392
393 // undo revisions.
394 $revisions_found = $this->undoRevisions( $chart_id, true );
395
396 // create revision for the edit action.
397 wp_save_post_revision( $chart_id );
398
399 if ( $revisions_found ) {
400 // fetch chart data again in case it was updated by an earlier revision.
401 $chart = get_post( $chart_id );
402 }
403 return $chart;
404 }
405
406 /**
407 * Returns the language of the locale.
408 *
409 * @access protected
410 */
411 protected function get_language() {
412 $locale = get_locale();
413 if ( empty( $locale ) ) {
414 return '';
415 }
416 $array = explode( '_', $locale );
417 if ( count( $array ) < 2 ) {
418 return '';
419 }
420 return reset( $array );
421 }
422
423 /**
424 * Gets/creates the JS where user-specific customizations can be/have been added.
425 */
426 protected function get_user_customization_js() {
427 // use this as the JS file in case we are not able to create the file in uploads.
428 $default = VISUALIZER_ABSURL . 'js/customization.js';
429
430 $uploads = wp_get_upload_dir();
431 $specific = $uploads['baseurl'] . '/visualizer/customization.js';
432
433 // for testing on user sites (before we send them the correctly customized file).
434 if ( VISUALIZER_TEST_JS_CUSTOMIZATION ) {
435 return $default;
436 }
437
438 require_once( ABSPATH . 'wp-admin/includes/file.php' );
439 WP_Filesystem();
440 global $wp_filesystem;
441
442 $dir = $wp_filesystem->wp_content_dir() . 'uploads/visualizer';
443 $file = $wp_filesystem->wp_content_dir() . 'uploads/visualizer/customization.js';
444
445 if ( $wp_filesystem->is_readable( $file ) ) {
446 return $specific;
447 }
448
449 if ( $wp_filesystem->exists( $file ) && ! $wp_filesystem->is_readable( $file ) ) {
450 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to read file %s', $file ), 'error', __FILE__, __LINE__ );
451 return $default;
452 }
453
454 if ( ! $wp_filesystem->exists( $dir ) ) {
455 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
456 if ( ( $done = $wp_filesystem->mkdir( $dir ) ) === false ) {
457 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to create directory %s', $dir ), 'error', __FILE__, __LINE__ );
458 return $default;
459 }
460 }
461
462 // if file does not exist, copy.
463 if ( ! $wp_filesystem->exists( $file ) ) {
464 $src = str_replace( ABSPATH, $wp_filesystem->abspath(), VISUALIZER_ABSPATH . '/js/customization.js' );
465 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
466 if ( ( $done = $wp_filesystem->copy( $src, $file ) ) === false ) {
467 do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to copy file %s to %s', $src, $file ), 'error', __FILE__, __LINE__ );
468 return $default;
469 }
470 }
471
472 return $specific;
473 }
474
475 /**
476 * Load the class for the given chart's chart type so that its assets can be loaded.
477 */
478 protected function load_chart_type( $chart_id ) {
479 $type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
480 $name = 'Visualizer_Render_Sidebar_Type_' . ucwords( $type );
481 $class = null;
482 if ( class_exists( $name ) || true === apply_filters( 'visualizer_load_chart', false, $name ) ) {
483 $class = new $name;
484 }
485
486 if ( is_null( $class ) && VISUALIZER_PRO ) {
487 // lets see if this type exists in pro. New Lite(3.1.0+) & old Pro(1.8.0-).
488 $class = apply_filters( 'visualizer_pro_chart_type_sidebar', null, array( 'type' => $type, 'settings' => get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true ) ) );
489 }
490
491 return is_null( $class ) ? null : $class->getLibrary();
492 }
493
494 /**
495 * Generates the inline CSS to apply to the chart and adds these classes to the settings.
496 *
497 * @access public
498 * @param int $id The id of the chart.
499 * @param array $settings The settings of the chart.
500 */
501 protected function get_inline_custom_css( $id, $settings ) {
502 $css = '';
503
504 $arguments = array( '', $settings );
505 if ( ! isset( $settings['customcss'] ) ) {
506 return $arguments;
507 }
508
509 $classes = array();
510 $css = '<style type="text/css" name="visualizer-custom-css" id="customcss-' . $id . '">';
511 foreach ( $settings['customcss'] as $name => $element ) {
512 $attributes = array();
513 foreach ( $element as $property => $value ) {
514 $attributes[] = $this->handle_css_property( $property, $value );
515 }
516 $class_name = $id . $name;
517 $properties = implode( '; ', array_filter( $attributes ) );
518 if ( ! empty( $properties ) ) {
519 $css .= '.' . $class_name . ' {' . $properties . ' !important;}';
520 $classes[ $name ] = $class_name;
521 }
522 }
523 $css .= '</style>';
524
525 $settings['cssClassNames'] = $classes;
526
527 $arguments = array( $css, $settings );
528 apply_filters_ref_array( 'visualizer_inline_css', array( &$arguments ) );
529
530 return $arguments;
531 }
532
533 /**
534 * Handles CSS properties that might need special syntax.
535 *
536 * @access private
537 * @param string $property The name of the css property.
538 * @param string $value The value of the css property.
539 */
540 private function handle_css_property( $property, $value ) {
541 if ( empty( $property ) || empty( $value ) ) {
542 return '';
543 }
544
545 switch ( $property ) {
546 case 'transform':
547 $value = 'rotate(' . $value . 'deg)';
548 break;
549 }
550 return $property . ': ' . $value;
551 }
552
553 /**
554 * Determines if charts have been created of the particular chart type.
555 */
556 protected static function hasChartType( $type ) {
557 $args = array(
558 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
559 'fields' => 'ids',
560 'post_status' => 'publish',
561 'meta_query' => array(
562 array(
563 'key' => Visualizer_Plugin::CF_CHART_TYPE,
564 'value' => $type,
565 ),
566 ),
567 );
568
569 $q = new WP_Query( $args );
570 return $q->found_posts > 0;
571 }
572
573 }
574