PluginProbe
TablePress – Tables in WordPress made easy / 3.0
TablePress – Tables in WordPress made easy v3.0
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
tablepress / classes / class-import-legacy.php

class-import-legacy.php in TablePress – Tables in WordPress made easy 3.0, at classes/class-import-legacy.php

351 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * TablePress Table Import Class
4 *
5 * @package TablePress
6 * @subpackage Export/Import
7 * @author Tobias Bäthge
8 * @since 1.0.0
9 */
10
11 // Prohibit direct script loading.
12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13
14 /**
15 * TablePress Table Import Class
16 *
17 * @package TablePress
18 * @subpackage Export/Import
19 * @author Tobias Bäthge
20 * @since 1.0.0
21 */
22 class TablePress_Import_Legacy extends TablePress_Import_Base {
23
24 /**
25 * File/Data Formats that are available for import.
26 *
27 * @since 1.0.0
28 * @var array<string, string>
29 */
30 public array $import_formats = array();
31
32 /**
33 * Whether HTML import support is available in the PHP installation on the server.
34 *
35 * @since 1.0.0
36 */
37 public bool $html_import_support_available = false;
38
39 /**
40 * Data to be imported.
41 *
42 * @since 1.0.0
43 */
44 protected string $import_data;
45
46 /**
47 * Imported table.
48 *
49 * @since 1.0.0
50 * @var array<string, mixed>|false
51 */
52 protected $imported_table = false;
53
54 /**
55 * Initialize the Import class.
56 *
57 * @since 1.0.0
58 */
59 public function __construct() {
60 if ( class_exists( 'DOMDocument', false ) && function_exists( 'simplexml_import_dom' ) && function_exists( 'libxml_use_internal_errors' ) ) {
61 $this->html_import_support_available = true;
62 }
63
64 // Initiate here, because function call not possible outside a class method.
65 $this->import_formats = array();
66 $this->import_formats['csv'] = __( 'CSV - Character-Separated Values', 'tablepress' );
67 if ( $this->html_import_support_available ) {
68 $this->import_formats['html'] = __( 'HTML - Hypertext Markup Language', 'tablepress' );
69 }
70 $this->import_formats['json'] = __( 'JSON - JavaScript Object Notation', 'tablepress' );
71 $this->import_formats['xls'] = __( 'XLS - Microsoft Excel 97-2003 (experimental)', 'tablepress' );
72 $this->import_formats['xlsx'] = __( 'XLSX - Microsoft Excel 2007-2019 (experimental)', 'tablepress' );
73 }
74
75 /**
76 * Import a table.
77 *
78 * @since 1.0.0
79 *
80 * @param string $format Import format.
81 * @param string $data Data to import.
82 * @return array<string, mixed>|false Table array on success, false on error.
83 */
84 public function import_table( string $format, string $data ) /* : array|false */ {
85 /**
86 * Filters the data that is to be imported.
87 *
88 * @since 1.8.1
89 *
90 * @param string $data Data to import.
91 * @param string $format Import format.
92 */
93 $this->import_data = apply_filters( 'tablepress_import_table_data', $data, $format );
94
95 if ( ! in_array( $format, array( 'xlsx', 'xls' ), true ) ) {
96 $this->fix_table_encoding();
97 }
98
99 switch ( $format ) {
100 case 'csv':
101 $this->import_csv();
102 break;
103 case 'html':
104 $this->import_html();
105 break;
106 case 'json':
107 $this->import_json();
108 break;
109 case 'xlsx':
110 $this->import_xlsx();
111 break;
112 case 'xls':
113 $this->import_xls();
114 break;
115 default:
116 return false;
117 }
118
119 if ( false === $this->imported_table ) {
120 return false;
121 }
122
123 // Make sure that cells are stored as strings.
124 array_walk_recursive(
125 $this->imported_table['data'],
126 static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void {
127 $cell_content = (string) $cell_content;
128 },
129 );
130
131 return $this->imported_table;
132 }
133
134 /**
135 * Import CSV data.
136 *
137 * @since 1.0.0
138 */
139 protected function import_csv(): void {
140 $csv_parser = TablePress::load_class( 'CSV_Parser', 'csv-parser.class.php', 'libraries' );
141 $csv_parser->load_data( $this->import_data );
142 $delimiter = $csv_parser->find_delimiter();
143 $data = $csv_parser->parse( $delimiter );
144 $this->pad_array_to_max_cols( $data );
145 $this->normalize_line_endings( $data );
146 $this->imported_table = array( 'data' => $data );
147 }
148
149 /**
150 * Import HTML data.
151 *
152 * @since 1.0.0
153 */
154 protected function import_html(): void {
155 if ( ! $this->html_import_support_available ) {
156 return;
157 }
158
159 TablePress::load_file( 'html-parser.class.php', 'libraries' );
160 $table = HTML_Parser::parse( $this->import_data );
161
162 if ( is_wp_error( $table ) ) {
163 $this->imported_table = false;
164 return;
165 }
166
167 $this->pad_array_to_max_cols( $table['data'] );
168 $this->normalize_line_endings( $table['data'] );
169 $this->imported_table = $table;
170 }
171
172 /**
173 * Import JSON data.
174 *
175 * @since 1.0.0
176 */
177 protected function import_json(): void {
178 $json_table = json_decode( $this->import_data, true );
179
180 // Check if JSON could be decoded.
181 if ( is_null( $json_table ) ) {
182 $json_error = json_last_error_msg();
183 $output = '<strong>' . __( 'The imported file contains errors:', 'tablepress' ) . "</strong><br><br>JSON error: {$json_error}<br>";
184 wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) );
185 }
186
187 // Specifically cast to an array again.
188 $json_table = (array) $json_table;
189
190 if ( isset( $json_table['data'] ) ) {
191 // JSON data contained a full export.
192 $table = $json_table;
193 } else {
194 // JSON data contained only the data of a table, but no options.
195 $table = array( 'data' => array() );
196 foreach ( $json_table as $row ) {
197 $table['data'][] = array_values( (array) $row );
198 }
199 }
200
201 $this->pad_array_to_max_cols( $table['data'] );
202 $this->imported_table = $table;
203 }
204
205 /**
206 * Import Microsoft Excel 97-2003 data.
207 *
208 * @since 1.1.0
209 */
210 protected function import_xls(): void {
211 $excel_reader = TablePress::load_class( 'Spreadsheet_Excel_Reader', 'excel-reader.class.php', 'libraries', $this->import_data );
212
213 // Loop through Excel file and retrieve value and colspan/rowspan properties for each cell.
214 $sheet = 0; // 0 means first sheet of the Workbook
215 $table = array();
216 $num_rows = $excel_reader->rowcount( $sheet );
217 $num_columns = $excel_reader->colcount( $sheet );
218 for ( $row = 1; $row <= $num_rows; $row++ ) {
219 $table_row = array();
220 for ( $column = 1; $column <= $num_columns; $column++ ) {
221 $cell = array();
222 $cell['rowspan'] = $excel_reader->rowspan( $row, $column, $sheet );
223 $cell['colspan'] = $excel_reader->colspan( $row, $column, $sheet );
224 $cell['val'] = $excel_reader->val( $row, $column, $sheet );
225 $table_row[] = $cell;
226 }
227 $table[] = $table_row;
228 }
229
230 // Transform colspan/rowspan properties to TablePress equivalent (cell content).
231 foreach ( $table as $row_idx => $row ) {
232 foreach ( $row as $col_idx => $cell ) {
233 if ( 1 === $cell['rowspan'] && 1 === $cell['colspan'] ) {
234 continue;
235 }
236
237 if ( 1 < $cell['colspan'] ) {
238 for ( $i = 1; $i < $cell['colspan']; $i++ ) {
239 $table[ $row_idx ][ $col_idx + $i ]['val'] = '#colspan#';
240 }
241 }
242 if ( 1 < $cell['rowspan'] ) {
243 for ( $i = 1; $i < $cell['rowspan']; $i++ ) {
244 $table[ $row_idx + $i ][ $col_idx ]['val'] = '#rowspan#';
245 }
246 }
247
248 if ( 1 < $cell['rowspan'] && 1 < $cell['colspan'] ) {
249 for ( $i = 1; $i < $cell['rowspan']; $i++ ) {
250 for ( $j = 1; $j < $cell['colspan']; $j++ ) {
251 $table[ $row_idx + $i ][ $col_idx + $j ]['val'] = '#span#';
252 }
253 }
254 }
255 }
256 }
257
258 // Flatten value property to two-dimensional array.
259 foreach ( $table as &$row ) {
260 foreach ( $row as &$cell ) {
261 $cell = $cell['val'];
262 }
263 unset( $cell ); // Unset use-by-reference parameter of foreach loop.
264 }
265 unset( $row ); // Unset use-by-reference parameter of foreach loop.
266
267 $this->imported_table = array( 'data' => $table );
268 }
269
270 /**
271 * Import Microsoft Excel 2007-2019 data.
272 *
273 * @since 1.1.0
274 */
275 protected function import_xlsx(): void {
276 TablePress::load_file( 'simplexlsx.class.php', 'libraries' );
277 $xlsx_file = \Shuchkin\SimpleXLSX::parse( $this->import_data, true );
278
279 if ( ! $xlsx_file ) {
280 $output = '<strong>' . __( 'The imported file contains errors:', 'tablepress' ) . '</strong><br><br>' . \Shuchkin\SimpleXLSX::parseError() . '<br>';
281 wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) );
282 }
283
284 $this->imported_table = array( 'data' => $xlsx_file->rows() );
285 }
286
287 /**
288 * Fixes the encoding to UTF-8 for the entire string that is to be imported.
289 *
290 * @since 1.0.0
291 *
292 * @link http://stevephillips.me/blog/dealing-php-and-character-encoding
293 */
294 protected function fix_table_encoding(): void {
295 // Check and remove possible UTF-8 Byte-Order Mark (BOM).
296 $bom = pack( 'CCC', 0xef, 0xbb, 0xbf );
297 if ( str_starts_with( $this->import_data, $bom ) ) {
298 $this->import_data = substr( $this->import_data, 3 );
299 // If data has a BOM, it's UTF-8, so further checks unnecessary.
300 return;
301 }
302
303 // Require the iconv() function for the following checks.
304 if ( ! function_exists( 'iconv' ) ) {
305 return;
306 }
307
308 // Check for possible UTF-16 BOMs ("little endian" and "big endian") and try to convert the data to UTF-8.
309 if ( str_starts_with( $this->import_data, "\xFF\xFE" ) || str_starts_with( $this->import_data, "\xFE\xFF" ) ) {
310 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
311 $data = @iconv( 'UTF-16', 'UTF-8', $this->import_data );
312 if ( false !== $data ) {
313 $this->import_data = $data;
314 return;
315 }
316 }
317
318 // Detect the character encoding and convert to UTF-8, if it's different.
319 if ( function_exists( 'mb_detect_encoding' ) ) {
320 $current_encoding = mb_detect_encoding( $this->import_data, 'ASCII, UTF-8, ISO-8859-1' );
321 if ( 'UTF-8' !== $current_encoding ) {
322 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
323 $data = @iconv( $current_encoding, 'UTF-8', $this->import_data ); // @phpstan-ignore argument.type
324 if ( false !== $data ) {
325 $this->import_data = $data;
326 return;
327 }
328 }
329 }
330 }
331
332 /**
333 * Replaces Windows line breaks \r\n with Unix line breaks \n.
334 *
335 * This function uses call by reference to save PHP memory on large arrays.
336 *
337 * @since 2.0.0
338 *
339 * @param array<int, array<int, string>> $an_array Array in which Windows line breaks should be replaced by Unix line breaks.
340 */
341 protected function normalize_line_endings( array &$an_array ): void {
342 array_walk_recursive(
343 $an_array,
344 static function ( string &$cell_content, int $col_idx ): void {
345 $cell_content = str_replace( "\r\n", "\n", $cell_content );
346 },
347 );
348 }
349
350 } // class TablePress_Import_Legacy
351