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

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