PluginProbe
TablePress – Tables in WordPress made easy / 3.0
TablePress – Tables in WordPress made easy v3.0
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
← All changes | classes/class-export.php +64 -32 1.9.23.0 View file →
@@ -12,8 +12,9 @@
12 12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 13
14 14 /**
15 15 * TablePress Table Export Class
16 + *
16 17 * @package TablePress
17 18 * @subpackage Export/Import
18 19 * @author Tobias Bäthge
19 20 * @since 1.0.0
@@ -23,27 +24,26 @@
23 24 /**
24 25 * File/Data Formats that are available for the export.
25 26 *
26 27 * @since 1.0.0
27 - * @var array
28 + * @var array<string, string>
28 29 */
29 - public $export_formats = array();
30 + public array $export_formats = array();
30 31
31 32 /**
32 33 * Delimiters for the CSV export.
33 34 *
34 35 * @since 1.0.0
35 - * @var array
36 + * @var array<string, string>
36 37 */
37 - public $csv_delimiters = array();
38 + public array $csv_delimiters = array();
38 39
39 40 /**
40 41 * Whether ZIP archive support is available in the PHP installation on the server.
41 42 *
42 43 * @since 1.0.0
43 - * @var bool
44 44 */
45 - public $zip_support_available = false;
45 + public bool $zip_support_available = false;
46 46
47 47 /**
48 48 * Initialize the Export class.
49 49 *
@@ -61,10 +61,9 @@
61 61 ',' => __( ', (comma)', 'tablepress' ),
62 62 'tab' => __( '\t (tabulator)', 'tablepress' ),
63 63 );
64 64
65 - /** This filter is documented in the WordPress function unzip_file() in wp-admin/includes/file.php */
66 - if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
65 + if ( class_exists( 'ZipArchive', false ) ) {
67 66 $this->zip_support_available = true;
68 67 }
69 68 }
70 69
@@ -72,14 +71,14 @@
72 71 * Export a table.
73 72 *
74 73 * @since 1.0.0
75 74 *
76 - * @param array $table Table to be exported.
77 - * @param string $export_format Format for the export ('csv', 'html', 'json').
78 - * @param string $csv_delimiter Delimiter for CSV export.
75 + * @param array<string, mixed> $table Table to be exported.
76 + * @param string $export_format Format for the export ('csv', 'html', 'json').
77 + * @param string $csv_delimiter Delimiter for CSV export.
79 78 * @return string Exported table (only data for CSV and HTML, full tables (including options) for JSON).
80 79 */
81 - public function export_table( array $table, $export_format, $csv_delimiter ) {
80 + public function export_table( array $table, string $export_format, string $csv_delimiter ): string {
82 81 switch ( $export_format ) {
83 82 case 'csv':
84 83 $output = '';
85 84 if ( 'tab' === $csv_delimiter ) {
@@ -101,15 +100,15 @@
101 100 $tfoot = '';
102 101 $tbody = array();
103 102
104 103 foreach ( $table['data'] as $row_idx => $row ) {
105 - // First row, need to check for head (but only if at least two rows).
106 - if ( 0 === $row_idx && $table['options']['table_head'] && $num_rows > 1 ) {
104 + // Table head rows, but only if there's at least one additional row.
105 + if ( $row_idx < $table['options']['table_head'] && $num_rows > $table['options']['table_head'] ) {
107 106 $thead = $this->html_render_row( $row, 'th' );
108 107 continue;
109 108 }
110 - // Last row, need to check for footer (but only if at least two rows).
111 - if ( $last_row_idx === $row_idx && $table['options']['table_foot'] && $num_rows > 1 ) {
109 + // Table foot rows, but only if there's at least one additional row.
110 + if ( $row_idx > $last_row_idx - $table['options']['table_foot'] && $num_rows > $table['options']['table_foot'] ) {
112 111 $tfoot = $this->html_render_row( $row, 'th' );
113 112 continue;
114 113 }
115 114 // Neither first nor last row (with respective head/foot enabled), so render as body row.
@@ -128,8 +127,11 @@
128 127 $output = "<table>\n" . $thead . $tfoot . $tbody . "</table>\n";
129 128 break;
130 129 case 'json':
131 130 $output = wp_json_encode( $table, TABLEPRESS_JSON_OPTIONS );
131 + if ( false === $output ) {
132 + $output = '';
133 + }
132 134 break;
133 135 default:
134 136 $output = '';
135 137 }
@@ -141,22 +143,52 @@
141 143 * Wrap and escape a cell for CSV export.
142 144 *
143 145 * @since 1.0.0
144 146 *
145 - * @param string $string Content of a cell.
146 - * @param string $delimiter CSV delimiter character.
147 - * @return string Wrapped string for CSV export
147 + * @param string $cell_content Content of a cell.
148 + * @param string $delimiter CSV delimiter character.
149 + * @return string Wrapped string for CSV export.
148 150 */
149 - protected function csv_wrap_and_escape( $string, $delimiter ) {
151 + protected function csv_wrap_and_escape( string $cell_content, string $delimiter ): string {
152 + // Return early if the cell is empty. No escaping or wrapping is needed then.
153 + if ( '' === $cell_content ) {
154 + return $cell_content;
155 + }
156 +
157 + // Escape potentially dangerous functions that could be used for CSV injection attacks in external spreadsheet software.
158 + $active_content_triggers = array( '=', '+', '-', '@' );
159 + if ( in_array( $cell_content[0], $active_content_triggers, true ) ) {
160 + $functions_to_escape = array(
161 + 'cmd|',
162 + 'rundll32',
163 + 'DDE(',
164 + 'IMPORTXML(',
165 + 'IMPORTFEED(',
166 + 'IMPORTHTML(',
167 + 'IMPORTRANGE(',
168 + 'IMPORTDATA(',
169 + 'IMAGE(',
170 + 'HYPERLINK(',
171 + 'WEBSERVICE(',
172 + );
173 + foreach ( $functions_to_escape as $function ) {
174 + if ( false !== stripos( $cell_content, $function ) ) {
175 + $cell_content = "'" . $cell_content; // Prepend a ' to indicate that the cell format is a text string.
176 + break;
177 + }
178 + }
179 + }
180 +
150 181 // Escape CSV delimiter for RegExp (e.g. '|').
151 182 $delimiter = preg_quote( $delimiter, '#' );
152 - if ( 1 === preg_match( '#' . $delimiter . '|"|\n|\r#i', $string ) || ' ' === substr( $string, 0, 1 ) || ' ' === substr( $string, -1 ) ) {
183 + if ( 1 === preg_match( '#' . $delimiter . '|"|\n|\r#i', $cell_content ) || str_starts_with( $cell_content, ' ' ) || str_ends_with( $cell_content, ' ' ) ) {
153 184 // Escape single " as double "".
154 - $string = str_replace( '"', '""', $string );
185 + $cell_content = str_replace( '"', '""', $cell_content );
155 186 // Wrap string in "".
156 - $string = '"' . $string . '"';
187 + $cell_content = '"' . $cell_content . '"';
157 188 }
158 - return $string;
189 +
190 + return $cell_content;
159 191 }
160 192
161 193 /**
162 194 * Generate the HTML of a row.
@@ -162,13 +194,13 @@
162 194 * Generate the HTML of a row.
163 195 *
164 196 * @since 1.0.0
165 197 *
166 - * @param array $row Cells of the row to be rendered.
167 - * @param string $tag HTML tag to use for the cells (td or th).
198 + * @param string[] $row Cells of the row to be rendered.
199 + * @param string $tag HTML tag to use for the cells (td or th).
168 200 * @return string HTML code for the row.
169 201 */
170 - protected function html_render_row( array $row, $tag ) {
202 + protected function html_render_row( array $row, string $tag ): string {
171 203 $output = "\t\t<tr>\n";
172 204 array_walk( $row, array( $this, 'html_wrap_and_escape' ), $tag );
173 205 $output .= implode( '', $row );
174 206 $output .= "\t\t</tr>\n";
@@ -179,18 +211,18 @@
179 211 * Wrap and escape a cell for HTML export.
180 212 *
181 213 * @since 1.0.0
182 214 *
183 - * @param string $cell_content Content of a cell.
184 - * @param int|null $column_idx Column index, or null if omitted. Unused, but defined to be able to use function as callback in array_walk().
185 - * @param string $html_tag HTML tag that shall be used for the cell.
215 + * @param string $cell_content Content of a cell.
216 + * @param int $column_idx Column index, or -1 if omitted. Unused, but defined to be able to use function as callback in array_walk().
217 + * @param string $html_tag HTML tag that shall be used for the cell.
186 218 */
187 - protected function html_wrap_and_escape( &$cell_content, $column_idx, $html_tag ) {
219 + protected function html_wrap_and_escape( string &$cell_content, int $column_idx, string $html_tag ): void {
188 220 /*
189 221 * Replace any & with &amp; that is not already an encoded entity (from function htmlentities2 in WP 2.8).
190 222 * A complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want.
191 223 */
192 - $cell_content = preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $cell_content );
224 + $cell_content = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $cell_content );
193 225 $cell_content = "\t\t\t<{$html_tag}>{$cell_content}</{$html_tag}>\n";
194 226 }
195 227
196 228 } // class TablePress_Export