PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.3
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.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 / Source / Xlsx.php

Xlsx.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.3, at classes/Visualizer/Source/Xlsx.php

151 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Source manager for local XLSX files.
4 *
5 * Reads the first sheet of an XLSX file using the OpenSpout library,
6 * which is already bundled as a dependency of this plugin.
7 * Legacy .xls (BIFF) files are not supported; OpenSpout's XLSX reader
8 * only handles the Office Open XML (.xlsx) format.
9 *
10 * Expected sheet layout (same convention as CSV import):
11 * Row 1 – column labels
12 * Row 2 – column types (string, number, boolean, date, datetime, timeofday)
13 * Row 3+ – data rows
14 *
15 * @category Visualizer
16 * @package Source
17 *
18 * @since 3.11.0
19 */
20 class Visualizer_Source_Xlsx extends Visualizer_Source {
21
22 /**
23 * The path to the XLSX file.
24 *
25 * @access protected
26 * @var string
27 */
28 protected $_filename;
29
30 /**
31 * Constructor.
32 *
33 * @access public
34 * @param string $filename Path to the XLSX file.
35 */
36 public function __construct( $filename = '' ) {
37 $this->_filename = trim( (string) $filename );
38 }
39
40 /**
41 * Fetches information from the XLSX file and builds the series/data arrays.
42 *
43 * @access public
44 * @return boolean TRUE on success, FALSE on failure.
45 */
46 public function fetch() {
47 if ( empty( $this->_filename ) ) {
48 $this->_error = esc_html__( 'No file provided. Please try again.', 'visualizer' );
49 return false;
50 }
51
52 if ( ! class_exists( 'OpenSpout\Reader\Common\Creator\ReaderEntityFactory' ) ) {
53 $this->_error = esc_html__( 'The OpenSpout library is required to import XLSX files but could not be found. Please contact support.', 'visualizer' );
54 return false;
55 }
56
57 $reader = \OpenSpout\Reader\Common\Creator\ReaderEntityFactory::createXLSXReader();
58 try {
59 $reader->open( $this->_get_file_path() );
60
61 $all_rows = array();
62 foreach ( $reader->getSheetIterator() as $sheet ) {
63 foreach ( $sheet->getRowIterator() as $row ) {
64 $row_data = array();
65 foreach ( $row->getCells() as $cell ) {
66 $value = $cell->getValue();
67 // Convert non-string scalars to string for uniform handling;
68 // _normalizeData() will cast them to the correct type later.
69 $row_data[] = is_null( $value ) ? null : (string) $value;
70 }
71 $all_rows[] = $row_data;
72 }
73 break; // Only read the first sheet.
74 }
75 } catch ( \Exception $e ) {
76 $reader->close();
77 $this->_error = sprintf(
78 /* translators: %s - the exception message. */
79 esc_html__( 'Could not read the XLSX file: %s', 'visualizer' ),
80 $e->getMessage()
81 );
82 return false;
83 }
84
85 $reader->close();
86
87 if ( count( $all_rows ) < 2 ) {
88 $this->_error = esc_html__( 'File should have a heading row (1st row) and a data type row (2nd row). Please try again.', 'visualizer' );
89 return false;
90 }
91
92 $labels = array_filter( $all_rows[0] );
93 $types = array_filter( $all_rows[1] );
94
95 if ( ! $labels || ! $types ) {
96 $this->_error = esc_html__( 'File should have a heading row (1st row) and a data type row (2nd row). Please try again.', 'visualizer' );
97 return false;
98 }
99
100 $types = array_map( 'trim', $types );
101 if ( ! self::_validateTypes( $types ) ) {
102 $this->_error = esc_html__( 'Invalid data types detected in the data type row (2nd row). Please try again.', 'visualizer' );
103 return false;
104 }
105
106 // Build the series array from row 1 (labels) and row 2 (types).
107 $label_values = $all_rows[0];
108 $type_values = $all_rows[1];
109 $col_count = count( $label_values );
110
111 for ( $i = 0; $i < $col_count; $i++ ) {
112 $default_type = ( $i === 0 ) ? 'string' : 'number';
113 $label = isset( $label_values[ $i ] ) ? $this->toUTF8( (string) $label_values[ $i ] ) : '';
114 $type = isset( $type_values[ $i ] ) && ! empty( $type_values[ $i ] ) ? trim( $type_values[ $i ] ) : $default_type;
115
116 $this->_series[] = array(
117 'label' => sanitize_text_field( wp_strip_all_tags( $label ) ),
118 'type' => $type,
119 );
120 }
121
122 // Parse data rows (row 3 onwards).
123 for ( $r = 2, $total = count( $all_rows ); $r < $total; $r++ ) {
124 $this->_data[] = $this->_normalizeData( $all_rows[ $r ] );
125 }
126
127 return true;
128 }
129
130 /**
131 * Returns the file path to open with the reader.
132 * Subclasses may override this to supply a locally-downloaded copy.
133 *
134 * @access protected
135 * @return string
136 */
137 protected function _get_file_path() {
138 return $this->_filename;
139 }
140
141 /**
142 * Returns the source name.
143 *
144 * @access public
145 * @return string
146 */
147 public function getSourceName() {
148 return __CLASS__;
149 }
150 }
151