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-export.php +67 -154 2.2.4 → 3.4 View file →
@@ -7,8 +7,10 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
@@ -21,14 +23,22 @@
21 23 */
22 24 class TablePress_Export {
23 25
24 26 /**
27 + * Available exporters for the export.
28 + *
29 + * @since 3.4.0
30 + * @var array<string, array{label: string, class: string, file: string, folder: string}>
31 + */
32 + protected array $exporters = array();
33 +
34 + /**
25 35 * File/Data Formats that are available for the export.
26 36 *
27 37 * @since 1.0.0
28 38 * @var array<string, string>
29 39 */
30 - public $export_formats = array();
40 + public array $export_formats = array();
31 41
32 42 /**
33 43 * Delimiters for the CSV export.
34 44 *
@@ -34,30 +44,58 @@
34 44 *
35 45 * @since 1.0.0
36 46 * @var array<string, string>
37 47 */
38 - public $csv_delimiters = array();
48 + public array $csv_delimiters = array();
39 49
40 50 /**
41 51 * Whether ZIP archive support is available in the PHP installation on the server.
42 52 *
43 53 * @since 1.0.0
44 - * @var bool
45 54 */
46 - public $zip_support_available = false;
55 + public bool $zip_support_available = false;
47 56
48 57 /**
49 - * Initialize the Export class.
58 + * Initializes the Export class.
50 59 *
51 60 * @since 1.0.0
52 61 */
53 62 public function __construct() {
54 63 // Initiate here, because function call not possible outside a class method.
55 - $this->export_formats = array(
56 - 'csv' => __( 'CSV - Character-Separated Values', 'tablepress' ),
57 - 'html' => __( 'HTML - Hypertext Markup Language', 'tablepress' ),
58 - 'json' => __( 'JSON - JavaScript Object Notation', 'tablepress' ),
64 + $this->exporters = array(
65 + 'csv' => array(
66 + 'label' => __( 'CSV - Character-Separated Values', 'tablepress' ),
67 + 'class' => \TablePress\Export\CSV_Exporter::class,
68 + 'file' => 'class-exporter-csv.php',
69 + 'folder' => 'classes/exporters',
70 + ),
71 + 'html' => array(
72 + 'label' => __( 'HTML - Hypertext Markup Language', 'tablepress' ),
73 + 'class' => \TablePress\Export\HTML_Exporter::class,
74 + 'file' => 'class-exporter-html.php',
75 + 'folder' => 'classes/exporters',
76 + ),
77 + 'json' => array(
78 + 'label' => __( 'JSON - JavaScript Object Notation', 'tablepress' ),
79 + 'class' => \TablePress\Export\JSON_Exporter::class,
80 + 'file' => 'class-exporter-json.php',
81 + 'folder' => 'classes/exporters',
82 + ),
59 83 );
84 +
85 + /**
86 + * Filters the available export formats.
87 + *
88 + * @since 3.4.0
89 + *
90 + * @param array<string, array{label: string, class: string, file: string, folder: string}> $exporters Associative array of available export formats.
91 + */
92 + $this->exporters = apply_filters( 'tablepress_exporters', $this->exporters );
93 +
94 + foreach ( $this->exporters as $format => $exporter ) {
95 + $this->export_formats[ $format ] = $exporter['label'];
96 + }
97 +
60 98 $this->csv_delimiters = array(
61 99 ';' => __( '; (semicolon)', 'tablepress' ),
62 100 ',' => __( ', (comma)', 'tablepress' ),
63 101 'tab' => __( '\t (tabulator)', 'tablepress' ),
@@ -62,169 +100,44 @@
62 100 ',' => __( ', (comma)', 'tablepress' ),
63 101 'tab' => __( '\t (tabulator)', 'tablepress' ),
64 102 );
65 103
66 - /** This filter is documented in the WordPress function unzip_file() in wp-admin/includes/file.php */
67 - if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
104 + if ( class_exists( 'ZipArchive', false ) ) {
68 105 $this->zip_support_available = true;
69 106 }
70 107 }
71 108
72 109 /**
73 - * Export a table.
110 + * Exports a table to the specified format using the corresponding exporter class.
74 111 *
75 112 * @since 1.0.0
113 + * @since 3.4.0 The $options parameter is now an array. If a string is passed, it is treated as the CSV delimiter (deprecated).
76 114 *
77 - * @param array<string, mixed> $table Table to be exported.
78 - * @param string $export_format Format for the export ('csv', 'html', 'json').
79 - * @param string $csv_delimiter Delimiter for CSV export.
80 - * @return string Exported table (only data for CSV and HTML, full tables (including options) for JSON).
115 + * @param array<string, mixed> $table Table to be exported.
116 + * @param string $export_format Format for the export, e.g. 'csv', 'html', or 'json'.
117 + * @param array<string, mixed>|string $options Options for the export if an array, or the CSV delimiter if a string. The latter is deprecated.
118 + * @return string Exported table data.
81 119 */
82 - public function export_table( array $table, string $export_format, string $csv_delimiter ): string {
83 - switch ( $export_format ) {
84 - case 'csv':
85 - $output = '';
86 - if ( 'tab' === $csv_delimiter ) {
87 - $csv_delimiter = "\t";
88 - }
89 - foreach ( $table['data'] as $row_idx => $row ) {
90 - $csv_row = array();
91 - foreach ( $row as $column_idx => $cell_content ) {
92 - $csv_row[] = $this->csv_wrap_and_escape( $cell_content, $csv_delimiter );
93 - }
94 - $output .= implode( $csv_delimiter, $csv_row );
95 - $output .= "\n";
96 - }
97 - break;
98 - case 'html':
99 - $num_rows = count( $table['data'] );
100 - $last_row_idx = $num_rows - 1;
101 - $thead = '';
102 - $tfoot = '';
103 - $tbody = array();
104 -
105 - foreach ( $table['data'] as $row_idx => $row ) {
106 - // First row, need to check for head (but only if at least two rows).
107 - if ( 0 === $row_idx && $table['options']['table_head'] && $num_rows > 1 ) {
108 - $thead = $this->html_render_row( $row, 'th' );
109 - continue;
110 - }
111 - // Last row, need to check for footer (but only if at least two rows).
112 - if ( $last_row_idx === $row_idx && $table['options']['table_foot'] && $num_rows > 1 ) {
113 - $tfoot = $this->html_render_row( $row, 'th' );
114 - continue;
115 - }
116 - // Neither first nor last row (with respective head/foot enabled), so render as body row.
117 - $tbody[] = $this->html_render_row( $row, 'td' );
118 - }
119 -
120 - // <thead>, <tfoot>, and <tbody> tags.
121 - if ( ! empty( $thead ) ) {
122 - $thead = "\t<thead>\n{$thead}\t</thead>\n";
123 - }
124 - if ( ! empty( $tfoot ) ) {
125 - $tfoot = "\t<tfoot>\n{$tfoot}\t</tfoot>\n";
126 - }
127 - $tbody = "\t<tbody>\n" . implode( '', $tbody ) . "\t</tbody>\n";
128 -
129 - $output = "<table>\n" . $thead . $tfoot . $tbody . "</table>\n";
130 - break;
131 - case 'json':
132 - $output = wp_json_encode( $table, TABLEPRESS_JSON_OPTIONS );
133 - if ( false === $output ) {
134 - $output = '';
135 - }
136 - break;
137 - default:
138 - $output = '';
120 + public function export_table( array $table, string $export_format, /* array|string */ $options ): string {
121 + if ( ! isset( $this->exporters[ $export_format ] ) ) {
122 + return '';
139 123 }
140 124
141 - return $output;
142 - }
143 -
144 - /**
145 - * Wrap and escape a cell for CSV export.
146 - *
147 - * @since 1.0.0
148 - *
149 - * @param string $cell_content Content of a cell.
150 - * @param string $delimiter CSV delimiter character.
151 - * @return string Wrapped string for CSV export.
152 - */
153 - protected function csv_wrap_and_escape( string $cell_content, string $delimiter ): string {
154 - // Return early if the cell is empty. No escaping or wrapping is needed then.
155 - if ( '' === $cell_content ) {
156 - return $cell_content;
125 + if ( ! is_array( $options ) ) {
126 + $options = array(
127 + 'csv_delimiter' => $options,
128 + );
157 129 }
158 130
159 - // Escape potentially dangerous functions that could be used for CSV injection attacks in external spreadsheet software.
160 - $active_content_triggers = array( '=', '+', '-', '@' );
161 - if ( in_array( $cell_content[0], $active_content_triggers, true ) ) {
162 - $functions_to_escape = array(
163 - 'cmd|',
164 - 'rundll32',
165 - 'DDE(',
166 - 'IMPORTXML(',
167 - 'IMPORTFEED(',
168 - 'IMPORTHTML(',
169 - 'IMPORTRANGE(',
170 - 'IMPORTDATA(',
171 - 'IMAGE(',
172 - 'HYPERLINK(',
173 - 'WEBSERVICE(',
174 - );
175 - foreach ( $functions_to_escape as $function ) {
176 - if ( false !== stripos( $cell_content, $function ) ) {
177 - $cell_content = "'" . $cell_content; // Prepend a ' to indicate that the cell format is a text string.
178 - break;
179 - }
180 - }
181 - }
131 + \TablePress::load_file( 'interface-exporter.php', 'classes/exporters' );
182 132
183 - // Escape CSV delimiter for RegExp (e.g. '|').
184 - $delimiter = preg_quote( $delimiter, '#' );
185 - if ( 1 === preg_match( '#' . $delimiter . '|"|\n|\r#i', $cell_content ) || str_starts_with( $cell_content, ' ' ) || str_ends_with( $cell_content, ' ' ) ) {
186 - // Escape single " as double "".
187 - $cell_content = str_replace( '"', '""', $cell_content );
188 - // Wrap string in "".
189 - $cell_content = '"' . $cell_content . '"';
133 + if ( '' !== $this->exporters[ $export_format ]['file'] ) {
134 + \TablePress::load_file( $this->exporters[ $export_format ]['file'], $this->exporters[ $export_format ]['folder'] );
190 135 }
191 136
192 - return $cell_content;
193 - }
137 + $exporter_class = $this->exporters[ $export_format ]['class'];
138 + $exporter = new $exporter_class();
194 139
195 - /**
196 - * Generate the HTML of a row.
197 - *
198 - * @since 1.0.0
199 - *
200 - * @param string[] $row Cells of the row to be rendered.
201 - * @param string $tag HTML tag to use for the cells (td or th).
202 - * @return string HTML code for the row.
203 - */
204 - protected function html_render_row( array $row, string $tag ): string {
205 - $output = "\t\t<tr>\n";
206 - array_walk( $row, array( $this, 'html_wrap_and_escape' ), $tag );
207 - $output .= implode( '', $row );
208 - $output .= "\t\t</tr>\n";
209 - return $output;
210 - }
211 -
212 - /**
213 - * Wrap and escape a cell for HTML export.
214 - *
215 - * @since 1.0.0
216 - *
217 - * @param string $cell_content Content of a cell.
218 - * @param int $column_idx Column index, or -1 if omitted. Unused, but defined to be able to use function as callback in array_walk().
219 - * @param string $html_tag HTML tag that shall be used for the cell.
220 - */
221 - protected function html_wrap_and_escape( string &$cell_content, int $column_idx, string $html_tag ): void {
222 - /*
223 - * Replace any & with &amp; that is not already an encoded entity (from function htmlentities2 in WP 2.8).
224 - * A complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want.
225 - */
226 - $cell_content = preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $cell_content );
227 - $cell_content = "\t\t\t<{$html_tag}>{$cell_content}</{$html_tag}>\n";
140 + return $exporter->export( $table, $options );
228 141 }
229 142
230 143 } // class TablePress_Export