PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 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 All 45 releases
tablepress / classes / class-import-legacy.php

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

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