PluginProbe
TablePress – Tables in WordPress made easy / 2.1.7
TablePress – Tables in WordPress made easy v2.1.7
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.php

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

780 lines 26.7 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 {
23
24 /**
25 * Instance of the TablePress Legacy Importer.
26 *
27 * @since 1.0.0
28 * @var TablePress_Import_Legacy
29 */
30 protected $importer;
31
32 /**
33 * Import configuration (mainly the data from the Import form).
34 *
35 * @since 2.0.0
36 * @var array
37 */
38 protected $import_config = array();
39
40 /**
41 * Whether ZIP archive support is available in the PHP installation on the server.
42 *
43 * @since 1.0.0
44 * @var bool
45 */
46 public $zip_support_available = false;
47
48 /**
49 * List of table names/IDs for use when replacing/appending existing tables (except for the JSON format).
50 *
51 * @since 2.0.0
52 * @var array
53 */
54 protected $table_names_ids = array();
55
56 /**
57 * Initializes the Import class.
58 *
59 * @since 1.0.0
60 */
61 public function __construct() {
62 /** This filter is documented in the WordPress function unzip_file() in wp-admin/includes/file.php */
63 if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
64 $this->zip_support_available = true;
65 }
66 }
67
68 /**
69 * Runs the import process for a given import configuration.
70 *
71 * @since 2.0.0
72 *
73 * @param array $import_config Import configuration.
74 * @return array|WP_Error List of imported tables on success, WP_Error on failure.
75 */
76 public function run( array $import_config ) {
77 // Unziping can use a lot of memory and execution time, but not this much hopefully.
78 wp_raise_memory_limit( 'admin' );
79 if ( function_exists( 'set_time_limit' ) ) {
80 @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
81 }
82
83 $this->import_config = $import_config;
84
85 $import_files = $this->_get_import_files();
86 if ( is_wp_error( $import_files ) ) {
87 return $import_files;
88 }
89
90 if ( in_array( $this->import_config['type'], array( 'replace', 'append' ), true ) ) {
91 $this->table_names_ids = $this->_get_list_of_table_names();
92 }
93
94 $import_files = $this->_convert_zip_files( $import_files );
95
96 return $this->_import_files( $import_files );
97 }
98
99 /**
100 * Extracts the files that shall be imported from the import configuration.
101 *
102 * @since 2.0.0
103 *
104 * @return array|WP_Error Files that shall be imported or WP_Error on failure.
105 */
106 protected function _get_import_files() {
107 $import_files = array();
108
109 switch ( $this->import_config['source'] ) {
110 case 'file-upload':
111 foreach ( $this->import_config['file-upload']['error'] as $key => $error ) {
112 $file = array(
113 'location' => $this->import_config['file-upload']['tmp_name'][ $key ],
114 'name' => $this->import_config['file-upload']['name'][ $key ],
115 );
116 if ( UPLOAD_ERR_OK !== $error ) {
117 @unlink( $this->import_config['file-upload']['tmp_name'][ $key ] );
118 $file['error'] = new WP_Error( 'table_import_file-upload_error', '', $error );
119 }
120 $import_files[] = $file;
121 }
122 break;
123 case 'url':
124 $host = wp_parse_url( $this->import_config['url'], PHP_URL_HOST );
125
126 if ( empty( $host ) ) {
127 return new WP_Error( 'table_import_url_host_invalid', '', $this->import_config['url'] );
128 }
129
130 // Check the host of the Import URL against a blacklist of hosts, which should not be accessible, e.g. for security considerations.
131 $blocked_hosts = array(
132 '169.254.169.254', // AWS Meta-data API.
133 );
134 if ( in_array( $host, $blocked_hosts, true ) ) {
135 return new WP_Error( 'table_import_url_host_blocked', '', $this->import_config['url'] );
136 }
137
138 /**
139 * Load WP file functions to be sure that `download_url()` exists, in particular during Cron requests.
140 */
141 require_once ABSPATH . 'wp-admin/includes/file.php';
142
143 // Download URL to local file.
144 $location = download_url( $this->import_config['url'] );
145 if ( is_wp_error( $location ) ) {
146 $error = new WP_Error( 'table_import_url_download_failed', '', $this->import_config['url'] );
147 $error->merge_from( $location );
148 return $error;
149 }
150
151 $import_files[] = array(
152 'location' => $location,
153 'name' => $this->import_config['url'],
154 );
155 break;
156 case 'server':
157 if ( ABSPATH === $this->import_config['server'] ) {
158 return new WP_Error( 'table_import_server_invalid', '', $this->import_config['server'] );
159 }
160
161 if ( ! is_readable( $this->import_config['server'] ) ) {
162 return new WP_Error( 'table_import_server_not_readable', '', $this->import_config['server'] );
163 }
164
165 $import_files[] = array(
166 'location' => $this->import_config['server'],
167 'name' => pathinfo( $this->import_config['server'], PATHINFO_BASENAME ),
168 'keep_file' => true, // Files on the server must not be deleted.
169 );
170 break;
171 case 'form-field':
172 $location = wp_tempnam();
173 $num_written_bytes = file_put_contents( $location, $this->import_config['form-field'] );
174 if ( false === $num_written_bytes || 0 === $num_written_bytes ) {
175 @unlink( $location );
176 return new WP_Error( 'table_import_form-field_temp_file_not_written' );
177 }
178
179 $import_files[] = array(
180 'location' => $location,
181 'name' => __( 'Imported from Manual Input', 'tablepress' ),
182 );
183 break;
184 default:
185 return new WP_Error( 'table_import_invalid_source', '', $this->import_config['source'] );
186 }
187
188 return $import_files;
189 }
190
191 /**
192 * Replaces ZIP archives in the import files with a list of their contents.
193 *
194 * ZIP files are removed from the list and their contents are added to the end of the list.
195 *
196 * @since 2.0.0
197 *
198 * @param array $import_files Files that shall be imported, including ZIP archives.
199 * @return array Files that shall be imported, with all ZIP archives recursively replaced by their contents.
200 */
201 protected function _convert_zip_files( array $import_files ) {
202 /*
203 * Here, a for loop is used over a foreach loop, as the array is modified while being iterated over.
204 * The foreach approach works in PHP 7+ (via https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.foreach.by-ref), but not in PHP 5.6.
205 * Once PHP 7.x is required, this can be adjusted again.
206 */
207 $num_files = count( $import_files ); // This number is growing inside the loop, if files are extracted from a ZIP file and appended to the list.
208 for ( $key = 0; $key < $num_files; $key++ ) {
209 $file = $import_files[ $key ];
210
211 if ( isset( $file['error'] ) && is_wp_error( $file['error'] ) ) {
212 continue;
213 }
214
215 $file['extension'] = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
216
217 if ( function_exists( 'mime_content_type' ) ) {
218 $file['mime_type'] = mime_content_type( $file['location'] );
219 if ( false === $file['mime_type'] ) {
220 $file['mime_type'] = '';
221 }
222 } else {
223 $file['mime_type'] = '';
224 }
225
226 // Detect ZIP files from their file extension or MIME type.
227 if ( 'zip' === $file['extension'] || 'application/zip' === $file['mime_type'] ) {
228 if ( ! $this->zip_support_available ) {
229 $file['error'] = new WP_Error( 'table_import_no_zip_support', '', $file['name'] );
230 $this->_maybe_unlink_file( $file );
231 continue;
232 }
233
234 $extracted_files = $this->_extract_zip_file( $file );
235 if ( is_wp_error( $extracted_files ) ) {
236 $file['error'] = $extracted_files->get_error_code();
237 $this->_maybe_unlink_file( $file );
238 continue;
239 }
240
241 if ( empty( $extracted_files ) ) {
242 $file['error'] = new WP_Error( 'table_import_zip_file_empty', '', $file['name'] );
243 $this->_maybe_unlink_file( $file );
244 continue;
245 }
246
247 $this->_maybe_unlink_file( $file );
248
249 // Mark the ZIP file as removed from the list (null), append its contents to the end, and increase the number of files counter.
250 $file = null;
251 array_push( $import_files, ...$extracted_files );
252 $num_files += count( $extracted_files );
253
254 }
255
256 $import_files[ $key ] = $file;
257 }
258
259 // Actually remove files that are marked as removed (null).
260 $import_files = array_filter(
261 $import_files,
262 static function( $file ) {
263 return ! is_null( $file );
264 }
265 );
266
267 $import_files = array_merge( $import_files ); // Re-index.
268
269 return $import_files;
270 }
271
272 /**
273 * Extracts the files of a ZIP files to a temporary folder and returns a list of files and their location.
274 *
275 * @since 2.0.0
276 *
277 * @param array $zip_file File data of a ZIP file (likely in a temporary folder).
278 * @return array|WP_Error List of files (name and location where they were extracted to) of the ZIP file or WP_Error on failure.
279 */
280 protected function _extract_zip_file( array $zip_file ) {
281 $zip = new ZipArchive();
282 $zip_opened = $zip->open( $zip_file['location'], ZIPARCHIVE::CHECKCONS );
283
284 // If the ZIP file can't be opened with ZIPARCHIVE::CHECKCONS, try again without.
285 if ( true !== $zip_opened ) {
286 $zip_opened = $zip->open( $zip_file['location'] );
287 }
288
289 // If the ZIP file can't even be opened without ZIPARCHIVE::CHECKCONS, bail.
290 if ( true !== $zip_opened ) {
291 return new WP_Error( 'table_import_error_zip_open', '', array( 'ziparchive_error' => $zip_opened ) );
292 }
293
294 $files = array();
295
296 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
297 for ( $file_idx = 0; $file_idx < $zip->numFiles; $file_idx++ ) {
298 $file_name = $zip->getNameIndex( $file_idx );
299
300 if ( false === $file_name ) {
301 $files[] = array(
302 'location' => '',
303 'name' => '',
304 'error' => new WP_Error( 'table_import_error_zip_stat', '', array( 'ziparchive_file_index' => $file_idx ) ),
305 );
306 continue;
307 }
308
309 // Skip directories.
310 if ( '/' === substr( $file_name, -1 ) ) {
311 continue;
312 }
313
314 // Skip the __MACOSX directory that macOS adds to archives.
315 if ( '__MACOSX/' === substr( $file_name, 0, 9 ) ) {
316 continue;
317 }
318
319 $file_data = $zip->getFromIndex( $file_idx );
320 if ( false === $file_data ) {
321 $files[] = array(
322 'location' => '',
323 'name' => $file_name,
324 'error' => new WP_Error( 'table_import_error_zip_get_data', '', array( 'ziparchive_file_index' => $file_idx, 'ziparchive_file_name' => $file_name ) ),
325 );
326 continue;
327 }
328
329 $location = wp_tempnam();
330 $num_written_bytes = file_put_contents( $location, $file_data );
331 if ( false === $num_written_bytes || 0 === $num_written_bytes ) {
332 @unlink( $location );
333 $files[] = array(
334 'location' => '',
335 'name' => $file_name,
336 'error' => new WP_Error( 'table_import_error_zip_write_temp_data', '', array( 'ziparchive_file_index' => $file_idx, 'ziparchive_file_name' => $file_name ) ),
337 );
338 continue;
339 }
340
341 $files[] = array(
342 'location' => $location,
343 'name' => $file_name,
344 );
345 }
346
347 $zip->close();
348
349 return $files;
350 }
351
352 /**
353 * Deletes a file unless the `keep_file` property is set to `true`.
354 *
355 * @since 2.0.0
356 *
357 * @param array $file File that should maybe be deleted.
358 */
359 protected function _maybe_unlink_file( array $file ) {
360 if ( ! ( isset( $file['keep_file'] ) && $file['keep_file'] ) && file_exists( $file['location'] ) ) {
361 @unlink( $file['location'] );
362 }
363 }
364
365 /**
366 * Prepares a list of table names/IDs for use when replacing/appending existing tables (except for the JSON format).
367 *
368 * @since 2.0.0
369 *
370 * @return array List of table names and IDs.
371 */
372 protected function _get_list_of_table_names() {
373 $existing_tables = array();
374 // Load all table IDs and names for a comparison with the file name.
375 $table_ids = TablePress::$model_table->load_all( false );
376 foreach ( $table_ids as $table_id ) {
377 // Load table, without table data, options, and visibility settings.
378 $table = TablePress::$model_table->load( $table_id, false, false );
379 if ( ! is_wp_error( $table ) ) {
380 $existing_tables[ $table['name'] ][] = $table['id']; // Attention: The table name is not unique!
381 }
382 }
383 return $existing_tables;
384 }
385
386 /**
387 * Checks whether the requirements for the PHPSpreadsheet import class are fulfilled or if the legacy import class should be used.
388 *
389 * @since 2.0.0
390 *
391 * @return bool Whether the legacy import class should be used.
392 */
393 protected function _should_use_legacy_import_class() {
394 // Allow overriding in the import config (coming e.g. from the import form UI).
395 if ( $this->import_config['legacy_import'] ) {
396 return true;
397 }
398
399 /**
400 * Filters whether the Legacy Table Import class shall be used.
401 *
402 * @since 2.0.0
403 *
404 * @param bool Whether to use the legacy table import class. Default false.
405 */
406 if ( apply_filters( 'tablepress_use_legacy_table_import_class', false ) ) {
407 return true;
408 }
409
410 // Use the legacy import class, if the requirements for PHPSpreadsheet are not fulfilled.
411 $phpspreadsheet_requirements_fulfilled = PHP_VERSION_ID >= 70200
412 && extension_loaded( 'mbstring' )
413 && class_exists( 'ZipArchive', false )
414 && class_exists( 'DOMDocument', false )
415 && function_exists( 'simplexml_load_string' )
416 && function_exists( 'libxml_disable_entity_loader' );
417 if ( ! $phpspreadsheet_requirements_fulfilled ) {
418 return true;
419 }
420
421 // Use the legacy import class, if the PHPSpreadsheet files do not exist (e.g. because `composer install` was not run).
422 if ( ! file_exists( TABLEPRESS_ABSPATH . 'libraries/autoload.php' ) ) {
423 return true;
424 }
425
426 return false;
427 }
428
429 /**
430 * Imports all found/extracted/configured files into TablePress.
431 *
432 * @since 2.0.0
433 *
434 * @param array $import_files Files that shall be imported.
435 * @return array Import tables and import errors.
436 */
437 protected function _import_files( array $import_files ) {
438 $tables = array();
439 $errors = array();
440
441 $use_legacy_import_class = $this->_should_use_legacy_import_class();
442
443 // Load Import Base Class.
444 TablePress::load_file( 'class-import-base.php', 'classes' );
445
446 // Choose the Table Import library based on the PHP version and the filter hook value.
447 if ( $use_legacy_import_class ) {
448 $this->importer = TablePress::load_class( 'TablePress_Import_Legacy', 'class-import-legacy.php', 'classes' );
449 } else {
450 $this->importer = TablePress::load_class( 'TablePress_Import_PHPSpreadsheet', 'class-import-phpspreadsheet.php', 'classes' );
451 }
452
453 // If there is more than one valid import file, ignore the chosen existing table for replacing/appending.
454 if ( in_array( $this->import_config['type'], array( 'replace', 'append' ), true ) && '' !== $this->import_config['existing_table'] ) {
455 $valid_import_files = 0;
456 foreach ( $import_files as $file ) {
457 if ( ! isset( $file['error'] ) || ! is_wp_error( $file['error'] ) ) {
458 ++$valid_import_files;
459 if ( $valid_import_files > 1 ) {
460 $this->import_config['existing_table'] = '';
461 break;
462 }
463 }
464 }
465 }
466
467 // Loop through all import files and import them.
468 foreach ( $import_files as $file ) {
469 if ( isset( $file['error'] ) && is_wp_error( $file['error'] ) ) {
470 $errors[] = $file;
471 continue;
472 }
473
474 // Use import method depending on chosen import class.
475 if ( $use_legacy_import_class ) {
476 $table = $this->_load_table_from_file_legacy( $file );
477 } else {
478 $table = $this->_load_table_from_file_phpspreadsheet( $file );
479 }
480
481 $this->_maybe_unlink_file( $file );
482
483 if ( is_wp_error( $table ) ) {
484 $file['error'] = $table;
485 $errors[] = $file;
486 continue;
487 }
488
489 $table = $this->_import_table( $table, $file );
490 if ( is_wp_error( $table ) ) {
491 $file['error'] = $table;
492 $errors[] = $file;
493 continue;
494 }
495
496 $tables[] = $table;
497 }
498
499 return array(
500 'tables' => $tables,
501 'errors' => $errors,
502 );
503 }
504
505 /**
506 * Loads a table from a file via the legacy import class.
507 *
508 * @since 2.0.0
509 *
510 * @param array $file File with the table data.
511 * @return array|WP_Error Loaded table on success (either with all properties or just 'data'), WP_Error on failure.
512 */
513 protected function _load_table_from_file_legacy( array $file ) {
514 // Guess the import format from the file extension.
515 switch ( $file['extension'] ) {
516 case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet.
517 case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded).
518 case 'xltx': // Excel (OfficeOpenXML) Template.
519 case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded).
520 $format = 'xlsx';
521 break;
522 case 'xls': // Excel (BIFF) Spreadsheet.
523 case 'xlt': // Excel (BIFF) Template.
524 $format = 'xls';
525 break;
526 case 'htm':
527 case 'html':
528 $format = 'html';
529 break;
530 case 'csv':
531 case 'tsv':
532 $format = 'csv';
533 break;
534 case 'json':
535 $format = 'json';
536 break;
537 default:
538 // If no format was found, pass the extension (which will likely result in an error).
539 $format = $file['extension'];
540 }
541
542 $data = file_get_contents( $file['location'] );
543 if ( false === $data ) {
544 return new WP_Error( 'table_import_legacy_data_read', '', $file['location'] );
545 }
546 if ( '' === $data ) {
547 return new WP_Error( 'table_import_legacy_data_empty', '', $file['location'] );
548 }
549
550 // If no format could be determined from the file extension, try guessing from the file content.
551 if ( '' === $format ) {
552 $first_character = $data[0];
553 if ( '<' === $first_character ) {
554 $format = 'html';
555 } elseif ( '{' === $first_character || '[' === $first_character ) {
556 $format = 'json';
557 }
558 }
559
560 // Fall back to CSV if no file format could be determined.
561 if ( '' === $format ) {
562 $format = 'csv';
563 }
564
565 if ( ! isset( $this->importer->import_formats[ $format ] ) ) {
566 return new WP_Error( 'table_import_legacy_unknown_format', '', $file['name'] );
567 }
568
569 $table = $this->importer->import_table( $format, $data );
570
571 if ( false === $table ) {
572 return new WP_Error( 'table_import_legacy_importer_failed', '', array( 'file_name' => $file['name'], 'file_format' => $format ) );
573 }
574
575 return $table;
576 }
577
578 /**
579 * Loads a table from a file via the PHPSpreadsheet import class.
580 *
581 * @since 2.0.0
582 *
583 * @param array $file File with the table data.
584 * @return array|WP_Error Loaded table on success (either with all properties or just 'data'), WP_Error on failure.
585 */
586 protected function _load_table_from_file_phpspreadsheet( array $file ) {
587 $table = $this->importer->import_table( $file );
588
589 if ( is_wp_error( $table ) ) {
590 return $table;
591 }
592
593 return $table;
594 }
595
596 /**
597 * Imports a loaded table into TablePress.
598 *
599 * @since 2.0.0
600 *
601 * @param array $table The table to be imported, either with properties or just the $table['data'] property set.
602 * @param array $file File with the table data.
603 * @return array|WP_Error Imported table on success, WP_Error on failure.
604 */
605 protected function _import_table( array $table, array $file ) {
606 // If name and description are imported from a new table, use those.
607 if ( ! isset( $table['name'] ) ) {
608 $table['name'] = $file['name'];
609 }
610 if ( ! isset( $table['description'] ) ) {
611 $table['description'] = $file['name'];
612 }
613
614 $import_type = $this->import_config['type'];
615 $existing_table_id = $this->import_config['existing_table'];
616
617 // If no existing table ID has been set (or if we are importing multiple tables), try to find a potential existing table from the table ID in the import data or by comparing the file name with the table name.
618 if ( in_array( $import_type, array( 'replace', 'append' ), true ) && '' === $existing_table_id ) {
619 if ( isset( $table['id'] ) ) {
620 // If the table already contained a table ID (e.g. for the JSON format), use that.
621 $existing_table_id = $table['id'];
622 } elseif ( isset( $this->table_names_ids[ $file['name'] ] ) && 1 === count( $this->table_names_ids[ $file['name'] ] ) ) {
623 // Use the replace/append ID of tables where the table name matches the file name, but only if there was exactly one file name match.
624 $existing_table_id = $this->table_names_ids[ $file['name'] ][0];
625 }
626 }
627
628 // If the table that is to be replaced or appended to does not exist, add the new table instead.
629 if ( ! TablePress::$model_table->table_exists( $existing_table_id ) ) {
630 $existing_table_id = '';
631 $import_type = 'add';
632 }
633
634 $table = $this->_import_tablepress_table( $table, $import_type, $existing_table_id );
635
636 return $table;
637 }
638
639 /**
640 * Imports a table by either replacing or appending to an existing table or by adding it as a new table.
641 *
642 * @since 1.0.0
643 *
644 * @param array $imported_table The table to be imported, either with properties or just the `name`, `description`, and `data` property set.
645 * @param string $import_type What to do with the imported data: "add", "replace", "append".
646 * @param string $existing_table_id Empty string if table shall be added as a new table, ID of the table to be replaced or appended to otherwise.
647 * @return array|WP_Error Table on success, WP_Error on error.
648 */
649 protected function _import_tablepress_table( array $imported_table, $import_type, $existing_table_id ) {
650 // Full JSON format table can contain a table ID, try to keep that, by later changing the imported table ID to this.
651 $table_id_in_import = isset( $imported_table['id'] ) ? $imported_table['id'] : '';
652
653 // To be able to replace or append to a table, the user must be able to edit the table, or it must be a Cron request (e.g. via the Automatic Periodic Table Import module).
654 if ( in_array( $import_type, array( 'replace', 'append' ), true ) && ! ( current_user_can( 'tablepress_edit_table', $existing_table_id ) || wp_doing_cron() ) ) {
655 return new WP_Error( 'table_import_replace_append_capability_check_failed', '', $existing_table_id );
656 }
657
658 switch ( $import_type ) {
659 case 'add':
660 $existing_table = TablePress::$model_table->get_table_template();
661 // Import visibility information if it exists, usually only for the JSON format.
662 if ( isset( $imported_table['visibility'] ) ) {
663 $existing_table['visibility'] = $imported_table['visibility'];
664 }
665 break;
666 case 'replace':
667 // Load table, without table data, but with options and visibility settings.
668 $existing_table = TablePress::$model_table->load( $existing_table_id, false, true );
669 if ( is_wp_error( $existing_table ) ) {
670 $error = new WP_Error( 'table_import_replace_table_load', '', $existing_table_id );
671 $error->merge_from( $existing_table );
672 return $error;
673 }
674 // Don't change name and description when a table is replaced.
675 $imported_table['name'] = $existing_table['name'];
676 $imported_table['description'] = $existing_table['description'];
677 // Replace visibility information if it exists.
678 if ( isset( $imported_table['visibility'] ) ) {
679 $existing_table['visibility'] = $imported_table['visibility'];
680 }
681 break;
682 case 'append':
683 // Load table, with table data, options, and visibility settings.
684 $existing_table = TablePress::$model_table->load( $existing_table_id, true, true );
685 if ( is_wp_error( $existing_table ) ) {
686 $error = new WP_Error( 'table_import_append_table_load', '', $existing_table_id );
687 $error->merge_from( $existing_table );
688 return $error;
689 }
690 if ( isset( $existing_table['is_corrupted'] ) && $existing_table['is_corrupted'] ) {
691 return new WP_Error( 'table_import_append_table_load_corrupted', '', $existing_table_id );
692 }
693 // Don't change name and description when a table is appended to.
694 $imported_table['name'] = $existing_table['name'];
695 $imported_table['description'] = $existing_table['description'];
696 // Actual appending:.
697 $imported_table['data'] = array_merge( $existing_table['data'], $imported_table['data'] );
698 $this->importer->pad_array_to_max_cols( $imported_table['data'] );
699 // Append visibility information for rows.
700 if ( isset( $imported_table['visibility']['rows'] ) ) {
701 $existing_table['visibility']['rows'] = array_merge( $existing_table['visibility']['rows'], $imported_table['visibility']['rows'] );
702 }
703 // When appending, do not overwrite options, e.g. coming from a JSON file.
704 unset( $imported_table['options'] );
705 break;
706 default:
707 return new WP_Error( 'table_import_import_type_invalid', '', $import_type );
708 }
709
710 // Merge new or existing table with information from the imported table.
711 $imported_table['id'] = $existing_table['id']; // Will be false for new table or the existing table ID.
712 // Cut visibility array (if the imported table is smaller), and pad correctly if imported table is bigger than existing table (or new template).
713 $num_rows = count( $imported_table['data'] );
714 $num_columns = count( $imported_table['data'][0] );
715 $imported_table['visibility'] = array(
716 'rows' => array_pad( array_slice( $existing_table['visibility']['rows'], 0, $num_rows ), $num_rows, 1 ),
717 'columns' => array_pad( array_slice( $existing_table['visibility']['columns'], 0, $num_columns ), $num_columns, 1 ),
718 );
719
720 // Check if the new table data is valid and consistent.
721 $table = TablePress::$model_table->prepare_table( $existing_table, $imported_table, false );
722 if ( is_wp_error( $table ) ) {
723 $error = new WP_Error( 'table_import_table_prepare', '', $imported_table['id'] );
724 $error->merge_from( $table );
725 return $error;
726 }
727
728 // DataTables Custom Commands can only be edit by trusted users.
729 if ( ! current_user_can( 'unfiltered_html' ) ) {
730 $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands'];
731 }
732
733 // Replace existing table or add new table.
734 if ( in_array( $import_type, array( 'replace', 'append' ), true ) ) {
735 // Replace existing table with imported/appended table.
736 $table_id = TablePress::$model_table->save( $table );
737 } else {
738 // Add the imported table (and get its first ID).
739 $table_id = TablePress::$model_table->add( $table );
740 }
741
742 if ( is_wp_error( $table_id ) ) {
743 $error = new WP_Error( 'table_import_table_save_or_add', '', $table['id'] );
744 $error->merge_from( $table_id );
745 return $error;
746 }
747
748 // Try to use ID from imported file (e.g. in full JSON format table).
749 if ( '' !== $table_id_in_import && $table_id !== $table_id_in_import && current_user_can( 'tablepress_edit_table_id', $table_id ) ) {
750 $id_changed = TablePress::$model_table->change_table_id( $table_id, $table_id_in_import );
751 if ( ! is_wp_error( $id_changed ) ) {
752 $table_id = $table_id_in_import;
753 }
754 }
755
756 $table['id'] = $table_id;
757
758 return $table;
759 }
760
761 /**
762 * Imports a table in legacy versions of the Table Auto Update Extension.
763 *
764 * This method is deprecated and is only left for backward compatibility reasons. Do not use this in new code!
765 *
766 * @since 1.0.0
767 * @deprecated 2.0.0 Use `run()` instead.
768 *
769 * @param string $format Import format.
770 * @param string $data Data to import.
771 * @return array|false Table array on success, false on error.
772 */
773 public function import_table( $format, $data ) {
774 TablePress::load_file( 'class-import-base.php', 'classes' );
775 $importer = TablePress::load_class( 'TablePress_Import_Legacy', 'class-import-legacy.php', 'classes' );
776 return $importer->import_table( $format, $data );
777 }
778
779 } // class TablePress_Import
780