PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 4.08
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v4.08
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / helpers / FrmCSVExportHelper.php

FrmCSVExportHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 4.08, at classes/helpers/FrmCSVExportHelper.php

454 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmCSVExportHelper {
7
8 protected static $separator = ', ';
9 protected static $column_separator = ',';
10 protected static $line_break = 'return';
11 protected static $charset = 'UTF-8';
12 protected static $to_encoding = 'UTF-8';
13 protected static $wp_date_format = 'Y-m-d H:i:s';
14 protected static $comment_count = 0;
15 protected static $form_id = 0;
16 protected static $headings = array();
17 protected static $fields = array();
18 protected static $entry;
19 protected static $has_parent_id;
20
21 public static function csv_format_options() {
22 $formats = array( 'UTF-8', 'ISO-8859-1', 'windows-1256', 'windows-1251', 'macintosh' );
23 $formats = apply_filters( 'frm_csv_format_options', $formats );
24
25 return $formats;
26 }
27
28 public static function generate_csv( $atts ) {
29 global $frm_vars;
30 $frm_vars['prevent_caching'] = true;
31
32 self::$fields = $atts['form_cols'];
33 self::$form_id = $atts['form']->id;
34 self::set_class_paramters();
35 self::set_has_parent_id( $atts['form'] );
36
37 $filename = apply_filters( 'frm_csv_filename', gmdate( 'ymdHis', time() ) . '_' . sanitize_title_with_dashes( $atts['form']->name ) . '_formidable_entries.csv', $atts['form'] );
38 unset( $atts['form'], $atts['form_cols'] );
39
40 self::print_file_headers( $filename );
41 unset( $filename );
42
43 $comment_count = FrmDb::get_count(
44 'frm_item_metas',
45 array(
46 'item_id' => $atts['entry_ids'],
47 'field_id' => 0,
48 'meta_value like' => '{',
49 ),
50 array(
51 'group_by' => 'item_id',
52 'order_by' => 'count(*) DESC',
53 'limit' => 1,
54 )
55 );
56 self::$comment_count = $comment_count;
57
58 self::prepare_csv_headings();
59
60 // fetch 20 posts at a time rather than loading the entire table into memory
61 while ( $next_set = array_splice( $atts['entry_ids'], 0, 20 ) ) {
62 self::prepare_next_csv_rows( $next_set );
63 }
64 }
65
66 private static function set_class_paramters() {
67 self::$separator = apply_filters( 'frm_csv_sep', self::$separator );
68 self::$line_break = apply_filters( 'frm_csv_line_break', self::$line_break );
69 self::$wp_date_format = apply_filters( 'frm_csv_date_format', self::$wp_date_format );
70 self::get_csv_format();
71 self::$charset = get_option( 'blog_charset' );
72
73 $col_sep = ( isset( $_POST['csv_col_sep'] ) && ! empty( $_POST['csv_col_sep'] ) ) ? sanitize_text_field( wp_unslash( $_POST['csv_col_sep'] ) ) : self::$column_separator;
74
75 self::$column_separator = apply_filters( 'frm_csv_column_sep', $col_sep );
76 }
77
78 private static function set_has_parent_id( $form ) {
79 self::$has_parent_id = $form->parent_form_id > 0;
80 }
81
82 private static function print_file_headers( $filename ) {
83 header( 'Content-Description: File Transfer' );
84 header( 'Content-Disposition: attachment; filename="' . esc_attr( $filename ) . '"' );
85 header( 'Content-Type: text/csv; charset=' . self::$charset, true );
86 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', mktime( gmdate( 'H' ) + 2, gmdate( 'i' ), gmdate( 's' ), gmdate( 'm' ), gmdate( 'd' ), gmdate( 'Y' ) ) ) . ' GMT' );
87 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
88 header( 'Cache-Control: no-cache, must-revalidate' );
89 header( 'Pragma: no-cache' );
90
91 do_action(
92 'frm_csv_headers',
93 array(
94 'form_id' => self::$form_id,
95 'fields' => self::$fields,
96 )
97 );
98 }
99
100 public static function get_csv_format() {
101 $csv_format = FrmAppHelper::get_post_param( 'csv_format', 'UTF-8', 'sanitize_text_field' );
102 $csv_format = apply_filters( 'frm_csv_format', $csv_format );
103 self::$to_encoding = $csv_format;
104 }
105
106 private static function prepare_csv_headings() {
107 $headings = array();
108 self::csv_headings( $headings );
109 $headings = apply_filters(
110 'frm_csv_columns',
111 $headings,
112 self::$form_id,
113 array(
114 'fields' => self::$fields,
115 )
116 );
117 self::$headings = $headings;
118
119 self::print_csv_row( $headings );
120 }
121
122 private static function field_headings( $col ) {
123 $field_headings = array();
124 $separate_values = array( 'user_id', 'file', 'data', 'date' );
125 if ( isset( $col->field_options['separate_value'] ) && $col->field_options['separate_value'] && ! in_array( $col->type, $separate_values, true ) ) {
126 $field_headings[ $col->id . '_label' ] = strip_tags( $col->name . ' ' . __( '(label)', 'formidable' ) );
127 }
128
129 $field_headings[ $col->id ] = strip_tags( $col->name );
130 $field_headings = apply_filters(
131 'frm_csv_field_columns',
132 $field_headings,
133 array(
134 'field' => $col,
135 )
136 );
137
138 return $field_headings;
139 }
140
141 private static function csv_headings( &$headings ) {
142 $fields_by_repeater_id = array();
143 $repeater_ids = array();
144
145 foreach ( self::$fields as $col ) {
146 if ( $col->form_id === self::$form_id || ! $col->field_options['in_section'] ) {
147 $headings += self::field_headings( $col );
148 } else {
149 $repeater_id = $col->field_options['in_section'];
150 $section = FrmField::getOne( $repeater_id );
151
152 if ( $section && FrmField::is_repeating_field( $section ) ) {
153 $headings[ 'repeater' . $repeater_id ] = array(); // set a placeholder to maintain order for repeater fields
154
155 if ( ! isset( $fields_by_repeater_id[ $repeater_id ] ) ) {
156 $fields_by_repeater_id[ $repeater_id ] = array();
157 $repeater_ids[] = $repeater_id;
158 }
159
160 $fields_by_repeater_id[ $repeater_id ][] = $col;
161 }
162 }
163 }
164 unset( $repeater_id, $col );
165
166 if ( $repeater_ids ) {
167 $where = array( 'field_id' => $repeater_ids );
168 $repeater_meta = FrmDb::get_results( 'frm_item_metas', $where, 'field_id, meta_value' );
169 $max = array_fill_keys( $repeater_ids, 0 );
170
171 foreach ( $repeater_meta as $row ) {
172 $start = strpos( $row->meta_value, 'a:' ) + 2;
173 $end = strpos( $row->meta_value, ':{' );
174 $length = substr( $row->meta_value, $start, $end - $start );
175
176 if ( $length > $max[ $row->field_id ] ) {
177 $max[ $row->field_id ] = $length;
178 }
179 }
180 unset( $start, $end, $length, $row, $repeater_meta, $where );
181
182 $flat = array();
183 foreach ( $headings as $key => $heading ) {
184 if ( is_array( $heading ) ) {
185 $repeater_id = str_replace( 'repeater', '', $key );
186
187 $repeater_headings = array();
188 foreach ( $fields_by_repeater_id[ $repeater_id ] as $col ) {
189 $repeater_headings += self::field_headings( $col );
190 }
191
192 for ( $i = 0; $i < $max[ $repeater_id ]; $i ++ ) {
193 foreach ( $repeater_headings as $repeater_key => $repeater_name ) {
194 $flat[ $repeater_key . '[' . $i . ']' ] = $repeater_name;
195 }
196 }
197 } else {
198 $flat[ $key ] = $heading;
199 }
200 }
201 unset( $key, $heading, $max, $repeater_headings, $repeater_id );
202
203 $headings = $flat;
204 unset( $flat );
205 }
206
207 if ( self::$comment_count ) {
208 for ( $i = 0; $i < self::$comment_count; $i ++ ) {
209 $headings[ 'comment' . $i ] = __( 'Comment', 'formidable' );
210 $headings[ 'comment_user_id' . $i ] = __( 'Comment User', 'formidable' );
211 $headings[ 'comment_created_at' . $i ] = __( 'Comment Date', 'formidable' );
212 }
213 unset( $i );
214 }
215
216 $headings['created_at'] = __( 'Timestamp', 'formidable' );
217 $headings['updated_at'] = __( 'Last Updated', 'formidable' );
218 $headings['user_id'] = __( 'Created By', 'formidable' );
219 $headings['updated_by'] = __( 'Updated By', 'formidable' );
220 $headings['is_draft'] = __( 'Draft', 'formidable' );
221 $headings['ip'] = __( 'IP', 'formidable' );
222 $headings['id'] = __( 'ID', 'formidable' );
223 $headings['item_key'] = __( 'Key', 'formidable' );
224 if ( self::has_parent_id() ) {
225 $headings['parent_id'] = __( 'Parent ID', 'formidable' );
226 }
227 }
228
229 private static function has_parent_id() {
230 return self::$has_parent_id;
231 }
232
233 private static function prepare_next_csv_rows( $next_set ) {
234 // order by parent_item_id so children will be first
235 $where = array(
236 'or' => 1,
237 'id' => $next_set,
238 'parent_item_id' => $next_set,
239 );
240 $entries = FrmEntry::getAll( $where, ' ORDER BY parent_item_id DESC', '', true, false );
241
242 foreach ( $entries as $k => $entry ) {
243 self::$entry = $entry;
244 unset( $entry );
245
246 if ( self::$entry->form_id !== self::$form_id ) {
247 self::add_repeat_field_values_to_csv( $entries );
248 } else {
249 self::prepare_csv_row();
250 }
251 }
252 }
253
254 private static function prepare_csv_row() {
255 $row = array();
256 self::add_field_values_to_csv( $row );
257 self::add_entry_data_to_csv( $row );
258 $row = apply_filters(
259 'frm_csv_row',
260 $row,
261 array(
262 'entry' => self::$entry,
263 'date_format' => self::$wp_date_format,
264 'comment_count' => self::$comment_count,
265 )
266 );
267 self::print_csv_row( $row );
268 }
269
270 private static function add_repeat_field_values_to_csv( &$entries ) {
271 if ( isset( self::$entry->metas ) ) {
272 // add child entries to the parent
273 foreach ( self::$entry->metas as $meta_id => $meta_value ) {
274 if ( ! is_numeric( $meta_id ) || '' === $meta_value ) {
275 // if the hook is being used to include field keys in the metas array,
276 // we need to skip the keys and only process field ids
277 continue;
278 }
279
280 if ( ! isset( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
281 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = array();
282 } elseif ( ! is_array( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
283 // if the data is here, it should be an array but if this field has collected data
284 // both while inside and outside of the repeating section, it's possible this is a string
285 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = (array) $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ];
286 }
287
288 //add the repeated values
289 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ][] = $meta_value;
290 }
291 $entries[ self::$entry->parent_item_id ]->metas += self::$entry->metas;
292 }
293
294 // add the embedded form id
295 if ( ! isset( $entries[ self::$entry->parent_item_id ]->embedded_fields ) ) {
296 $entries[ self::$entry->parent_item_id ]->embedded_fields = array();
297 }
298 $entries[ self::$entry->parent_item_id ]->embedded_fields[ self::$entry->id ] = self::$entry->form_id;
299 }
300
301 private static function add_field_values_to_csv( &$row ) {
302 foreach ( self::$fields as $col ) {
303 $field_value = isset( self::$entry->metas[ $col->id ] ) ? self::$entry->metas[ $col->id ] : false;
304
305 FrmAppHelper::unserialize_or_decode( $field_value );
306 self::add_array_values_to_columns( $row, compact( 'col', 'field_value' ) );
307
308 $field_value = apply_filters(
309 'frm_csv_value',
310 $field_value,
311 array(
312 'field' => $col,
313 'entry' => self::$entry,
314 'separator' => self::$separator,
315 )
316 );
317
318 if ( ! empty( $col->field_options['separate_value'] ) ) {
319 $sep_value = FrmEntriesHelper::display_value(
320 $field_value,
321 $col,
322 array(
323 'type' => $col->type,
324 'post_id' => self::$entry->post_id,
325 'show_icon' => false,
326 'entry_id' => self::$entry->id,
327 'sep' => self::$separator,
328 'embedded_field_id' => ( isset( self::$entry->embedded_fields ) && isset( self::$entry->embedded_fields[ self::$entry->id ] ) ) ? 'form' . self::$entry->embedded_fields[ self::$entry->id ] : 0,
329 )
330 );
331
332 $row[ $col->id . '_label' ] = $sep_value;
333 unset( $sep_value );
334 }
335
336 $row[ $col->id ] = $field_value;
337
338 unset( $col, $field_value );
339 }
340 }
341
342 /**
343 * @since 2.0.23
344 */
345 private static function add_array_values_to_columns( &$row, $atts ) {
346 if ( is_array( $atts['field_value'] ) ) {
347 foreach ( $atts['field_value'] as $key => $sub_value ) {
348 $column_key = $atts['col']->id . '_' . $key;
349 if ( ! is_numeric( $key ) && isset( self::$headings[ $column_key ] ) ) {
350 $row[ $column_key ] = $sub_value;
351 }
352 }
353 }
354 }
355
356 private static function add_entry_data_to_csv( &$row ) {
357 $row['created_at'] = FrmAppHelper::get_formatted_time( self::$entry->created_at, self::$wp_date_format, ' ' );
358 $row['updated_at'] = FrmAppHelper::get_formatted_time( self::$entry->updated_at, self::$wp_date_format, ' ' );
359 $row['user_id'] = self::$entry->user_id;
360 $row['updated_by'] = self::$entry->updated_by;
361 $row['is_draft'] = self::$entry->is_draft ? '1' : '0';
362 $row['ip'] = self::$entry->ip;
363 $row['id'] = self::$entry->id;
364 $row['item_key'] = self::$entry->item_key;
365 if ( self::has_parent_id() ) {
366 $row['parent_id'] = self::$entry->parent_item_id;
367 }
368 }
369
370 private static function print_csv_row( $rows ) {
371 $sep = '';
372
373 foreach ( self::$headings as $k => $heading ) {
374 if ( isset( $rows[ $k ] ) ) {
375 $row = $rows[ $k ];
376 } else {
377 $row = '';
378 // array indexed data is not at $rows[ $k ]
379 if ( $k[ strlen( $k ) - 1 ] === ']' ) {
380 $start = strrpos( $k, '[' );
381 $key = substr( $k, 0, $start ++ );
382 $index = substr( $k, $start, strlen( $k ) - 1 - $start );
383
384 if ( isset( $rows[ $key ] ) && isset( $rows[ $key ][ $index ] ) ) {
385 $row = $rows[ $key ][ $index ];
386 }
387
388 unset( $start, $key, $index );
389 }
390 }
391
392 if ( is_array( $row ) ) {
393 // implode the repeated field values
394 $row = implode( self::$separator, FrmAppHelper::array_flatten( $row, 'reset' ) );
395 }
396
397 $val = self::encode_value( $row );
398 if ( 'return' !== self::$line_break ) {
399 $val = str_replace( array( "\r\n", "\r", "\n" ), self::$line_break, $val );
400 }
401
402 echo $sep . '"' . $val . '"'; // WPCS: XSS ok.
403 $sep = self::$column_separator;
404
405 unset( $k, $row );
406 }
407 echo "\n";
408 }
409
410 public static function encode_value( $line ) {
411 if ( '' === $line ) {
412 return $line;
413 }
414
415 $convmap = false;
416
417 switch ( self::$to_encoding ) {
418 case 'macintosh':
419 // this map was derived from the differences between the MacRoman and UTF-8 Charsets
420 // Reference:
421 // - http://www.alanwood.net/demos/macroman.html
422 $convmap = array( 256, 304, 0, 0xffff, 306, 337, 0, 0xffff, 340, 375, 0, 0xffff, 377, 401, 0, 0xffff, 403, 709, 0, 0xffff, 712, 727, 0, 0xffff, 734, 936, 0, 0xffff, 938, 959, 0, 0xffff, 961, 8210, 0, 0xffff, 8213, 8215, 0, 0xffff, 8219, 8219, 0, 0xffff, 8227, 8229, 0, 0xffff, 8231, 8239, 0, 0xffff, 8241, 8248, 0, 0xffff, 8251, 8259, 0, 0xffff, 8261, 8363, 0, 0xffff, 8365, 8481, 0, 0xffff, 8483, 8705, 0, 0xffff, 8707, 8709, 0, 0xffff, 8711, 8718, 0, 0xffff, 8720, 8720, 0, 0xffff, 8722, 8729, 0, 0xffff, 8731, 8733, 0, 0xffff, 8735, 8746, 0, 0xffff, 8748, 8775, 0, 0xffff, 8777, 8799, 0, 0xffff, 8801, 8803, 0, 0xffff, 8806, 9673, 0, 0xffff, 9675, 63742, 0, 0xffff, 63744, 64256, 0, 0xffff );
423 break;
424 case 'ISO-8859-1':
425 $convmap = array( 256, 10000, 0, 0xffff );
426 }
427
428 if ( is_array( $convmap ) ) {
429 $line = mb_encode_numericentity( $line, $convmap, self::$charset );
430 }
431
432 if ( self::$to_encoding !== self::$charset ) {
433 $line = iconv( self::$charset, self::$to_encoding . '//IGNORE', $line );
434 }
435
436 return self::escape_csv( $line );
437 }
438
439 /**
440 * Escape a " in a csv with another "
441 *
442 * @since 2.0
443 */
444 public static function escape_csv( $value ) {
445 if ( '=' === $value[0] ) {
446 // escape the = to prevent vulnerability
447 $value = "'" . $value;
448 }
449 $value = str_replace( '"', '""', $value );
450
451 return $value;
452 }
453 }
454