0byt3m1n1
Path:
/
var
/
www
/
clients
/
client38
/
web37
/
web
/
wp-content
/
plugins
/
tablepress
/
classes
/
[
Home
]
File: class-import.php
<?php /** * TablePress Table Import Class * * @package TablePress * @subpackage Export/Import * @author Tobias Bäthge * @since 1.0.0 */ // Prohibit direct script loading. defined( 'ABSPATH' ) || die( 'No direct script access allowed!' ); /** * TablePress Table Import Class * @package TablePress * @subpackage Export/Import * @author Tobias Bäthge * @since 1.0.0 */ class TablePress_Import { /** * File/Data Formats that are available for import. * * @since 1.0.0 * @var array */ public $import_formats = array(); /** * Whether ZIP archive support is available in the PHP installation on the server. * * @since 1.0.0 * @var bool */ public $zip_support_available = false; /** * Whether HTML import support is available in the PHP installation on the server. * * @since 1.0.0 * @var bool */ public $html_import_support_available = false; /** * Data to be imported. * * @since 1.0.0 * @var string */ protected $import_data; /** * Imported table. * * @since 1.0.0 * @var array|false */ protected $imported_table = false; /** * Initialize the Import class. * * @since 1.0.0 */ public function __construct() { /** This filter is documented in the WordPress function unzip_file() in wp-admin/includes/file.php */ if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) { $this->zip_support_available = true; } if ( class_exists( 'DOMDocument', false ) && function_exists( 'simplexml_import_dom' ) && function_exists( 'libxml_use_internal_errors' ) ) { $this->html_import_support_available = true; } // Initiate here, because function call not possible outside a class method. $this->import_formats = array(); $this->import_formats['csv'] = __( 'CSV - Character-Separated Values', 'tablepress' ); if ( $this->html_import_support_available ) { $this->import_formats['html'] = __( 'HTML - Hypertext Markup Language', 'tablepress' ); } $this->import_formats['json'] = __( 'JSON - JavaScript Object Notation', 'tablepress' ); $this->import_formats['xls'] = __( 'XLS - Microsoft Excel 97-2003 (experimental)', 'tablepress' ); $this->import_formats['xlsx'] = __( 'XLSX - Microsoft Excel 2007-2019 (experimental)', 'tablepress' ); } /** * Import a table. * * @since 1.0.0 * * @param string $format Import format. * @param string $data Data to import. * @return array|false Table array on success, false on error. */ public function import_table( $format, $data ) { $this->import_data = apply_filters( 'tablepress_import_table_data', $data, $format ); if ( ! in_array( $format, array( 'xlsx', 'xls' ), true ) ) { $this->fix_table_encoding(); } switch ( $format ) { case 'csv': $this->import_csv(); break; case 'html': $this->import_html(); break; case 'json': $this->import_json(); break; case 'xlsx': $this->import_xlsx(); break; case 'xls': $this->import_xls(); break; default: return false; } return $this->imported_table; } /** * Import CSV data. * * @since 1.0.0 */ protected function import_csv() { $csv_parser = TablePress::load_class( 'CSV_Parser', 'csv-parser.class.php', 'libraries' ); $csv_parser->load_data( $this->import_data ); $delimiter = $csv_parser->find_delimiter(); $data = $csv_parser->parse( $delimiter ); $this->imported_table = array( 'data' => $this->pad_array_to_max_cols( $data ) ); } /** * Import HTML data. * * @since 1.0.0 */ protected function import_html() { if ( ! $this->html_import_support_available ) { return false; } if ( false === stripos( $this->import_data, '<table' ) || false === stripos( $this->import_data, '</table>' ) ) { $this->imported_table = false; return; } // Prepend XML declaration, for better encoding support. $full_html = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . $this->import_data; if ( function_exists( 'libxml_disable_entity_loader' ) ) { /* * Don't expand external entities, see https://websec.io/2012/08/27/Preventing-XXE-in-PHP.html. * Silence warnings as the function is deprecated in PHP 8, but can be necessary with LIBXML_NOENT being defined, see https://core.trac.wordpress.org/changeset/50714. */ @libxml_disable_entity_loader( true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } // No warnings/errors raised, but stored internally. libxml_use_internal_errors( true ); $dom = new DOMDocument( '1.0', 'UTF-8' ); // No strict checking for invalid HTML. $dom->strictErrorChecking = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $dom->loadHTML( $full_html ); if ( false === $dom ) { $this->imported_table = false; return; } $dom_tables = $dom->getElementsByTagName( 'table' ); if ( 0 === count( $dom_tables ) ) { $this->imported_table = false; return; } libxml_clear_errors(); // Clear errors so that we only catch those inside the table in the next line. $table = simplexml_import_dom( $dom_tables->item( 0 ) ); if ( false === $table ) { $this->imported_table = false; return; } $errors = libxml_get_errors(); libxml_clear_errors(); if ( ! empty( $errors ) ) { $output = '<strong>' . __( 'The imported file contains errors:', 'tablepress' ) . '</strong><br /><br />'; foreach ( $errors as $error ) { switch ( $error->level ) { case LIBXML_ERR_WARNING: $output .= "Warning {$error->code}: {$error->message} in line {$error->line}, column {$error->column}<br />"; break; case LIBXML_ERR_ERROR: $output .= "Error {$error->code}: {$error->message} in line {$error->line}, column {$error->column}<br />"; break; case LIBXML_ERR_FATAL: $output .= "Fatal Error {$error->code}: {$error->message} in line {$error->line}, column {$error->column}<br />"; break; } } wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } $html_table = array( 'data' => array(), 'options' => array(), ); if ( isset( $table->thead ) ) { $html_table['data'] = array_merge( $html_table['data'], $this->_import_html_rows( $table->thead[0]->tr ) ); $html_table['options']['table_head'] = true; } if ( isset( $table->tbody ) ) { $html_table['data'] = array_merge( $html_table['data'], $this->_import_html_rows( $table->tbody[0]->tr ) ); } if ( isset( $table->tr ) ) { $html_table['data'] = array_merge( $html_table['data'], $this->_import_html_rows( $table->tr ) ); } if ( isset( $table->tfoot ) ) { $html_table['data'] = array_merge( $html_table['data'], $this->_import_html_rows( $table->tfoot[0]->tr ) ); $html_table['options']['table_foot'] = true; } $html_table['data'] = $this->pad_array_to_max_cols( $html_table['data'] ); $this->imported_table = $html_table; } /** * Helper for HTML import. * * @since 1.0.0 * * @param SimpleXMLElement $element XMLElement. * @return array SimpleXMLElement exported to an array. */ protected function _import_html_rows( $element ) { $rows = array(); foreach ( $element as $row ) { $new_row = array(); foreach ( $row as $cell ) { // Get text between <td>...</td>, or <th>...</th>, possibly with attributes. if ( 1 === preg_match( '#<t(?:d|h).*?>(.*)</t(?:d|h)>#is', $cell->asXML(), $matches ) ) { /* * Decode HTML entities again, as there might be some left especially in attributes of HTML tags in the cells, * see https://secure.php.net/manual/en/simplexmlelement.asxml.php#107137. */ $matches[1] = html_entity_decode( $matches[1], ENT_NOQUOTES, 'UTF-8' ); $new_row[] = $matches[1]; // Look for colspan and add correct number of cells. if ( 1 === preg_match( '#<t(?:d|h).*colspan="(\d+)".*?>#is', $cell->asXml(), $matches ) ) { for ( $i = 1; $i < (int) $matches[1]; $i++ ) { $new_row[] = '#colspan#'; } } } else { $new_row[] = ''; } } $rows[] = $new_row; } return $rows; } /** * Import JSON data. * * @since 1.0.0 */ protected function import_json() { $json_table = json_decode( $this->import_data, true ); // Check if JSON could be decoded. if ( is_null( $json_table ) ) { // If possible, try to find out what error prevented the JSON from being decoded. $json_error = 'The error could not be determined.'; $json_error_msg = json_last_error_msg(); if ( false !== $json_error_msg ) { $json_error = $json_error_msg; } $output = '<strong>' . __( 'The imported file contains errors:', 'tablepress' ) . "</strong><br /><br />JSON error: {$json_error}<br />"; wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } // Specifically cast to an array again. $json_table = (array) $json_table; if ( isset( $json_table['data'] ) ) { // JSON data contained a full export. $table = $json_table; } else { // JSON data contained only the data of a table, but no options. $table = array( 'data' => array() ); foreach ( $json_table as $row ) { $table['data'][] = array_values( (array) $row ); } } $table['data'] = $this->pad_array_to_max_cols( $table['data'] ); $this->imported_table = $table; } /** * Import Microsoft Excel 97-2003 data. * * @since 1.1.0 */ protected function import_xls() { $excel_reader = TablePress::load_class( 'Spreadsheet_Excel_Reader', 'excel-reader.class.php', 'libraries', $this->import_data ); // Loop through Excel file and retrieve value and colspan/rowspan properties for each cell. $sheet = 0; // 0 means first sheet of the Workbook $table = array(); for ( $row = 1; $row <= $excel_reader->rowcount( $sheet ); $row++ ) { $table_row = array(); for ( $col = 1; $col <= $excel_reader->colcount( $sheet ); $col++ ) { $cell = array(); $cell['rowspan'] = $excel_reader->rowspan( $row, $col, $sheet ); $cell['colspan'] = $excel_reader->colspan( $row, $col, $sheet ); $cell['val'] = $excel_reader->val( $row, $col, $sheet ); $table_row[] = $cell; } $table[] = $table_row; } // Transform colspan/rowspan properties to TablePress equivalent (cell content). foreach ( $table as $row_idx => $row ) { foreach ( $row as $col_idx => $cell ) { if ( 1 === $cell['rowspan'] && 1 === $cell['colspan'] ) { continue; } if ( 1 < $cell['colspan'] ) { for ( $i = 1; $i < $cell['colspan']; $i++ ) { $table[ $row_idx ][ $col_idx + $i ]['val'] = '#colspan#'; } } if ( 1 < $cell['rowspan'] ) { for ( $i = 1; $i < $cell['rowspan']; $i++ ) { $table[ $row_idx + $i ][ $col_idx ]['val'] = '#rowspan#'; } } if ( 1 < $cell['rowspan'] && 1 < $cell['colspan'] ) { for ( $i = 1; $i < $cell['rowspan']; $i++ ) { for ( $j = 1; $j < $cell['colspan']; $j++ ) { $table[ $row_idx + $i ][ $col_idx + $j ]['val'] = '#span#'; } } } } } // Flatten value property to two-dimensional array. $result_table = array(); foreach ( $table as $row_idx => $row ) { $table_row = array(); foreach ( $row as $col_idx => $cell ) { $table_row[] = (string) $cell['val']; } $result_table[] = $table_row; } $this->imported_table = array( 'data' => $this->pad_array_to_max_cols( $result_table ) ); } /** * Import Microsoft Excel 2007-2019 data. * * @since 1.1.0 */ protected function import_xlsx() { TablePress::load_file( 'simplexlsx.class.php', 'libraries' ); $xlsx_file = SimpleXLSX::parse( $this->import_data, true ); if ( ! $xlsx_file ) { $output = '<strong>' . __( 'The imported file contains errors:', 'tablepress' ) . '</strong><br /><br />' . SimpleXLSX::parseError() . '<br />'; wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } $this->imported_table = array( 'data' => $xlsx_file->rows() ); } /** * Make sure array is rectangular with $max_cols columns in every row. * * @since 1.0.0 * * @param array $array Two-dimensional array to be padded. * @return array Padded array. */ public function pad_array_to_max_cols( array $array ) { $rows = count( $array ); $rows = ( $rows > 0 ) ? $rows : 1; $max_columns = $this->count_max_columns( $array ); $max_columns = ( $max_columns > 0 ) ? $max_columns : 1; // array_map wants arrays as additional parameters, so we create one with the max_columns to pad to and one with the value to use (empty string). $max_columns_array = array_fill( 1, $rows, $max_columns ); $pad_values_array = array_fill( 1, $rows, '' ); return array_map( 'array_pad', $array, $max_columns_array, $pad_values_array ); } /** * Get the highest number of columns in the rows. * * @since 1.0.0 * * @param array $array Two-dimensional array. * @return int Highest number of columns in the rows of the array. */ protected function count_max_columns( array $array ) { $max_columns = 0; foreach ( $array as $row_idx => $row ) { $num_columns = count( $row ); $max_columns = max( $num_columns, $max_columns ); } return $max_columns; } /** * Fixes the encoding to UTF-8 for the entire string that is to be imported. * * @since 1.0.0 * * @link http://stevephillips.me/blog/dealing-php-and-character-encoding */ protected function fix_table_encoding() { // Check and remove possible UTF-8 Byte-Order Mark (BOM). $bom = pack( 'CCC', 0xef, 0xbb, 0xbf ); if ( 0 === strncmp( $this->import_data, $bom, 3 ) ) { $this->import_data = substr( $this->import_data, 3 ); // If data has a BOM, it's UTF-8, so further checks unnecessary. return; } // Require the iconv() function for the following checks. if ( ! function_exists( 'iconv' ) ) { return; } // Check for possible UTF-16 BOMs ("little endian" and "big endian") and try to convert the data to UTF-8. if ( "\xFF\xFE" === substr( $this->import_data, 0, 2 ) || "\xFE\xFF" === substr( $this->import_data, 0, 2 ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $data = @iconv( 'UTF-16', 'UTF-8', $this->import_data ); if ( false !== $data ) { $this->import_data = $data; return; } } // Detect the character encoding and convert to UTF-8, if it's different. if ( function_exists( 'mb_detect_encoding' ) ) { $current_encoding = mb_detect_encoding( $this->import_data, 'ASCII, UTF-8, ISO-8859-1' ); if ( 'UTF-8' !== $current_encoding ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $data = @iconv( $current_encoding, 'UTF-8', $this->import_data ); if ( false !== $data ) { $this->import_data = $data; return; } } } } } // class TablePress_Import