PluginProbe
TablePress – Tables in WordPress made easy / 3.3.3
TablePress – Tables in WordPress made easy v3.3.3
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.3.3, at classes/class-import-legacy.php

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