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
← All changes | classes/class-import-phpspreadsheet.php +51 -23 2.3.1 → 3.4 View file →
@@ -7,8 +7,10 @@
7 7 * @author Tobias Bäthge
8 8 * @since 2.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 use TablePress\Import\File;
12 14
13 15 // Prohibit direct script loading.
14 16 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
@@ -29,9 +31,9 @@
29 31 * @since 2.0.0
30 32 */
31 33 public function __construct() {
32 34 // Load PHPSpreadsheet via its autoloading mechanism.
33 - TablePress::load_file( 'autoload.php', 'libraries' );
35 + TablePress::load_file( 'autoload.php', 'libraries/vendor' );
34 36 }
35 37
36 38 /**
37 39 * Imports a table from a file.
@@ -57,14 +59,14 @@
57 59 return new WP_Error( 'table_import_phpspreadsheet_data_empty', '', $file->location );
58 60 }
59 61
60 62 $table = $this->_maybe_import_json( $data );
61 - if ( false !== $table ) {
63 + if ( is_array( $table ) ) {
62 64 return $table;
63 65 }
64 66
65 67 $table = $this->_maybe_import_html( $data );
66 - if ( false !== $table ) {
68 + if ( is_array( $table ) ) {
67 69 return $table;
68 70 }
69 71
70 72 return $this->_import_phpspreadsheet( $file );
@@ -129,9 +131,9 @@
129 131 *
130 132 * @since 2.0.0
131 133 *
132 134 * @param string $data Data to import.
133 - * @return array<string, mixed>|false Table array on success, false if the file is not an HTML file.
135 + * @return array<string, mixed>|WP_Error Table array on success, WP_Error if the file is not an HTML file.
134 136 */
135 137 protected function _maybe_import_html( string $data ) /* : array|false */ {
136 138 TablePress::load_file( 'html-parser.class.php', 'libraries' );
137 139 $table = HTML_Parser::parse( $data );
@@ -137,9 +139,9 @@
137 139 $table = HTML_Parser::parse( $data );
138 140
139 141 // Check if the HTML code could be parsed. If not, this is probably not an HTML file.
140 142 if ( is_wp_error( $table ) ) {
141 - return false;
143 + return $table;
142 144 }
143 145
144 146 $this->pad_array_to_max_cols( $table['data'] );
145 147 return $table;
@@ -155,13 +157,22 @@
155 157 */
156 158 protected function _import_phpspreadsheet( File $file ) /* : array|WP_Error */ {
157 159 // Rename the temporary file, as PHPSpreadsheet tries to infer the format from the file's extension.
158 160 if ( '' !== $file->extension ) {
159 - $temp_file = pathinfo( $file->location );
160 - if ( ! isset( $temp_file['extension'] ) || $file->extension !== $temp_file['extension'] ) {
161 - $new_location = "{$temp_file['dirname']}/{$temp_file['filename']}.{$file->extension}"; // @phpstan-ignore-line
162 - if ( rename( $file->location, $new_location ) ) {
163 - $file->location = $new_location;
161 + $file_data = pathinfo( $file->location );
162 + if ( ! isset( $file_data['extension'] ) || $file->extension !== $file_data['extension'] ) {
163 + $temp_file = wp_tempnam();
164 + $new_location = "{$temp_file}.{$file->extension}";
165 + if ( $file->keep_file ) {
166 + // Copy the file, as the original should be kept.
167 + if ( copy( $file->location, $new_location ) ) {
168 + $file->location = $new_location;
169 + $file->keep_file = false; // Delete the newly created file after the import.
170 + }
171 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
172 + if ( rename( $file->location, $new_location ) ) {
173 + $file->location = $new_location;
174 + }
164 175 }
165 176 }
166 177 }
167 178
@@ -167,9 +178,9 @@
167 178
168 179 try {
169 180 // Treat all cell values as strings, except for formulas (due to recognition of quoted/escaped formulas like `'=A2`).
170 181 \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \TablePress\PhpOffice\PhpSpreadsheet\Cell\StringValueBinder() );
171 - \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::getValueBinder()->setFormulaConversion( false ); // @phpstan-ignore-line
182 + \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::getValueBinder()->setFormulaConversion( false ); // @phpstan-ignore method.notFound
172 183
173 184 /*
174 185 * Try to detect a reader from the file extension and MIME type.
175 186 * Fall back to CSV if no reader could be determined.
@@ -177,12 +188,21 @@
177 188 try {
178 189 $reader = \TablePress\PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile( $file->location );
179 190 } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception $exception ) {
180 191 $reader = \TablePress\PhpOffice\PhpSpreadsheet\IOFactory::createReader( 'Csv' );
181 - // Append .csv to the file name, so that \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::canRead() returns true.
182 - $new_location = $file->location . '.csv';
183 - if ( rename( $file->location, $new_location ) ) {
184 - $file->location = $new_location;
192 + // Change the file extension to .csv, so that \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::canRead() returns true.
193 + $temp_file = wp_tempnam();
194 + $new_location = "{$temp_file}.csv";
195 + if ( $file->keep_file ) {
196 + // Copy the file, as the original should be kept.
197 + if ( copy( $file->location, $new_location ) ) {
198 + $file->location = $new_location;
199 + $file->keep_file = false; // Delete the newly created file after the import.
200 + }
201 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
202 + if ( rename( $file->location, $new_location ) ) {
203 + $file->location = $new_location;
204 + }
185 205 }
186 206 }
187 207
188 208 $class_name = get_class( $reader );
@@ -189,10 +209,10 @@
189 209 $class_type = explode( '\\', $class_name );
190 210 $detected_format = strtolower( array_pop( $class_type ) );
191 211
192 212 if ( 'csv' === $detected_format ) {
193 - $reader->setInputEncoding( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::GUESS_ENCODING ); // @phpstan-ignore-line
194 - // @phpstan-ignore-next-line
213 + $reader->setInputEncoding( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::GUESS_ENCODING ); // @phpstan-ignore method.notFound
214 + // @phpstan-ignore method.notFound, smaller.alwaysFalse (PHPStan thinks that the Composer minimum version will always be fulfilled.)
195 215 $reader->setEscapeCharacter( ( PHP_VERSION_ID < 70400 ) ? "\x0" : '' ); // Disable the proprietary escape mechanism of PHP's fgetcsv() in PHP >= 7.4.
196 216 }
197 217
198 218 $reader->setIncludeCharts( false );
@@ -204,9 +224,9 @@
204 224 }
205 225
206 226 // For formats where it's supported, import only the first sheet.
207 227 if ( in_array( $detected_format, array( 'csv', 'html', 'slk' ), true ) ) {
208 - $reader->setSheetIndex( 0 ); // @phpstan-ignore-line
228 + $reader->setSheetIndex( 0 ); // @phpstan-ignore method.notFound
209 229 }
210 230
211 231 $spreadsheet = $reader->load( $file->location );
212 232 $worksheet = $spreadsheet->getActiveSheet();
@@ -222,12 +242,12 @@
222 242 $max_col = $worksheet->getHighestColumn();
223 243 $max_row = $worksheet->getHighestRow();
224 244
225 245 // Adapted from \TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::rangeToArray().
226 - ++$max_col; // Due to for-loop with characters for columns.
246 + \TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper::stringIncrement( $max_col ); // Due to for-loop with characters for columns.
227 247 for ( $row = $min_row; $row <= $max_row; $row++ ) {
228 248 $row_data = array();
229 - for ( $col = $min_col; $col !== $max_col; $col++ ) {
249 + for ( $col = $min_col; $col !== $max_col; \TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper::stringIncrement( $col ) ) {
230 250 $cell_reference = $col . $row;
231 251 if ( ! $cell_collection->has( $cell_reference ) ) {
232 252 $row_data[] = '';
233 253 continue;
@@ -252,8 +272,16 @@
252 272 $style = $spreadsheet->getCellXfByIndex( $cell->getXfIndex() );
253 273
254 274 $format = $style->getNumberFormat()->getFormatCode() ?? \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL;
255 275
276 + /*
277 + * When cells in Excel files are formatted as "Text", quotation marks are removed, due to https://github.com/PHPOffice/PhpSpreadsheet/pull/3344.
278 + * Setting the format to "General" seems to prevent that.
279 + */
280 + if ( \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT === $format && ! is_numeric( $cell_data ) ) {
281 + $format = \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL;
282 + }
283 +
256 284 // Fix floating point precision issues with numbers in the "General" Excel .xlsx format.
257 285 if ( 'xlsx' === $detected_format && \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL === $format && is_numeric( $cell_data ) ) {
258 286 $cell_data = (string) (float) $cell_data; // Type-cast strings to float and back.
259 287 }
@@ -259,13 +287,13 @@
259 287 }
260 288 $cell_data = \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::toFormattedString(
261 289 $cell_data,
262 290 $format,
263 - array( $this, 'format_color' )
291 + array( $this, 'format_color' ),
264 292 );
265 293
266 294 if ( strlen( $cell_data ) > 1 && '=' === $cell_data[0] ) {
267 - if ( $style->getQuotePrefix() ) {
295 + if ( 'xlsx' === $detected_format && $style->getQuotePrefix() ) {
268 296 // Prepend a ' to quoted/escaped formulas (so that they are shown as text). This is currently not supported (at least) for the XLS format.
269 297 $cell_data = "'{$cell_data}";
270 298 } else {
271 299 // Bail early, to not add inline HTML styling around formulas, as they won't work anymore then.
@@ -352,9 +380,9 @@
352 380 $spreadsheet->disconnectWorksheets();
353 381 unset( $comments, $cell_collection, $worksheet, $spreadsheet );
354 382
355 383 return $table;
356 - } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception $exception ) {
384 + } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception | \TablePress\PhpOffice\PhpSpreadsheet\Exception $exception ) {
357 385 return new WP_Error( 'table_import_phpspreadsheet_failed', '', 'Exception: ' . $exception->getMessage() );
358 386 }
359 387 }
360 388