PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 4.07.01
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v4.07.01
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.07.01, at classes/helpers/FrmCSVExportHelper.php

372 lines 12.5 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 csv_headings( &$headings ) {
123 foreach ( self::$fields as $col ) {
124 $field_headings = array();
125 $separate_values = array( 'user_id', 'file', 'data', 'date' );
126 if ( isset( $col->field_options['separate_value'] ) && $col->field_options['separate_value'] && ! in_array( $col->type, $separate_values, true ) ) {
127 $field_headings[ $col->id . '_label' ] = strip_tags( $col->name . ' ' . __( '(label)', 'formidable' ) );
128 }
129
130 $field_headings[ $col->id ] = strip_tags( $col->name );
131 $field_headings = apply_filters(
132 'frm_csv_field_columns',
133 $field_headings,
134 array(
135 'field' => $col,
136 )
137 );
138
139 $headings += $field_headings;
140 }
141
142 if ( self::$comment_count ) {
143 for ( $i = 0; $i < self::$comment_count; $i ++ ) {
144 $headings[ 'comment' . $i ] = __( 'Comment', 'formidable' );
145 $headings[ 'comment_user_id' . $i ] = __( 'Comment User', 'formidable' );
146 $headings[ 'comment_created_at' . $i ] = __( 'Comment Date', 'formidable' );
147 }
148 unset( $i );
149 }
150
151 $headings['created_at'] = __( 'Timestamp', 'formidable' );
152 $headings['updated_at'] = __( 'Last Updated', 'formidable' );
153 $headings['user_id'] = __( 'Created By', 'formidable' );
154 $headings['updated_by'] = __( 'Updated By', 'formidable' );
155 $headings['is_draft'] = __( 'Draft', 'formidable' );
156 $headings['ip'] = __( 'IP', 'formidable' );
157 $headings['id'] = __( 'ID', 'formidable' );
158 $headings['item_key'] = __( 'Key', 'formidable' );
159 if ( self::has_parent_id() ) {
160 $headings['parent_id'] = __( 'Parent ID', 'formidable' );
161 }
162 }
163
164 private static function has_parent_id() {
165 return self::$has_parent_id;
166 }
167
168 private static function prepare_next_csv_rows( $next_set ) {
169 // order by parent_item_id so children will be first
170 $where = array(
171 'or' => 1,
172 'id' => $next_set,
173 'parent_item_id' => $next_set,
174 );
175 $entries = FrmEntry::getAll( $where, ' ORDER BY parent_item_id DESC', '', true, false );
176
177 foreach ( $entries as $k => $entry ) {
178 self::$entry = $entry;
179 unset( $entry );
180
181 if ( self::$entry->form_id !== self::$form_id ) {
182 self::add_repeat_field_values_to_csv( $entries );
183 } else {
184 self::prepare_csv_row();
185 }
186 }
187 }
188
189 private static function prepare_csv_row() {
190 $row = array();
191 self::add_field_values_to_csv( $row );
192 self::add_entry_data_to_csv( $row );
193 $row = apply_filters(
194 'frm_csv_row',
195 $row,
196 array(
197 'entry' => self::$entry,
198 'date_format' => self::$wp_date_format,
199 'comment_count' => self::$comment_count,
200 )
201 );
202 self::print_csv_row( $row );
203 }
204
205 private static function add_repeat_field_values_to_csv( &$entries ) {
206 if ( isset( self::$entry->metas ) ) {
207 // add child entries to the parent
208 foreach ( self::$entry->metas as $meta_id => $meta_value ) {
209 if ( ! is_numeric( $meta_id ) || '' === $meta_value ) {
210 // if the hook is being used to include field keys in the metas array,
211 // we need to skip the keys and only process field ids
212 continue;
213 }
214
215 if ( ! isset( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
216 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = array();
217 } elseif ( ! is_array( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
218 // if the data is here, it should be an array but if this field has collected data
219 // both while inside and outside of the repeating section, it's possible this is a string
220 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = (array) $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ];
221 }
222
223 //add the repeated values
224 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ][] = $meta_value;
225 }
226 $entries[ self::$entry->parent_item_id ]->metas += self::$entry->metas;
227 }
228
229 // add the embedded form id
230 if ( ! isset( $entries[ self::$entry->parent_item_id ]->embedded_fields ) ) {
231 $entries[ self::$entry->parent_item_id ]->embedded_fields = array();
232 }
233 $entries[ self::$entry->parent_item_id ]->embedded_fields[ self::$entry->id ] = self::$entry->form_id;
234 }
235
236 private static function add_field_values_to_csv( &$row ) {
237 foreach ( self::$fields as $col ) {
238 $field_value = isset( self::$entry->metas[ $col->id ] ) ? self::$entry->metas[ $col->id ] : false;
239
240 FrmAppHelper::unserialize_or_decode( $field_value );
241 self::add_array_values_to_columns( $row, compact( 'col', 'field_value' ) );
242
243 $field_value = apply_filters(
244 'frm_csv_value',
245 $field_value,
246 array(
247 'field' => $col,
248 'entry' => self::$entry,
249 'separator' => self::$separator,
250 )
251 );
252
253 if ( isset( $col->field_options['separate_value'] ) && $col->field_options['separate_value'] ) {
254 $sep_value = FrmEntriesHelper::display_value(
255 $field_value,
256 $col,
257 array(
258 'type' => $col->type,
259 'post_id' => self::$entry->post_id,
260 'show_icon' => false,
261 'entry_id' => self::$entry->id,
262 'sep' => self::$separator,
263 '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,
264 )
265 );
266
267 $row[ $col->id . '_label' ] = $sep_value;
268 unset( $sep_value );
269 }
270
271 $row[ $col->id ] = $field_value;
272
273 unset( $col, $field_value );
274 }
275 }
276
277 /**
278 * @since 2.0.23
279 */
280 private static function add_array_values_to_columns( &$row, $atts ) {
281 if ( is_array( $atts['field_value'] ) ) {
282 foreach ( $atts['field_value'] as $key => $sub_value ) {
283 $column_key = $atts['col']->id . '_' . $key;
284 if ( ! is_numeric( $key ) && isset( self::$headings[ $column_key ] ) ) {
285 $row[ $column_key ] = $sub_value;
286 }
287 }
288 }
289 }
290
291 private static function add_entry_data_to_csv( &$row ) {
292 $row['created_at'] = FrmAppHelper::get_formatted_time( self::$entry->created_at, self::$wp_date_format, ' ' );
293 $row['updated_at'] = FrmAppHelper::get_formatted_time( self::$entry->updated_at, self::$wp_date_format, ' ' );
294 $row['user_id'] = self::$entry->user_id;
295 $row['updated_by'] = self::$entry->updated_by;
296 $row['is_draft'] = self::$entry->is_draft ? '1' : '0';
297 $row['ip'] = self::$entry->ip;
298 $row['id'] = self::$entry->id;
299 $row['item_key'] = self::$entry->item_key;
300 if ( self::has_parent_id() ) {
301 $row['parent_id'] = self::$entry->parent_item_id;
302 }
303 }
304
305 private static function print_csv_row( $rows ) {
306 $sep = '';
307
308 foreach ( self::$headings as $k => $heading ) {
309 $row = isset( $rows[ $k ] ) ? $rows[ $k ] : '';
310 if ( is_array( $row ) ) {
311 // implode the repeated field values
312 $row = implode( self::$separator, FrmAppHelper::array_flatten( $row, 'reset' ) );
313 }
314
315 $val = self::encode_value( $row );
316 if ( 'return' !== self::$line_break ) {
317 $val = str_replace( array( "\r\n", "\r", "\n" ), self::$line_break, $val );
318 }
319
320 echo $sep . '"' . $val . '"'; // WPCS: XSS ok.
321 $sep = self::$column_separator;
322
323 unset( $k, $row );
324 }
325 echo "\n";
326 }
327
328 public static function encode_value( $line ) {
329 if ( '' === $line ) {
330 return $line;
331 }
332
333 $convmap = false;
334
335 switch ( self::$to_encoding ) {
336 case 'macintosh':
337 // this map was derived from the differences between the MacRoman and UTF-8 Charsets
338 // Reference:
339 // - http://www.alanwood.net/demos/macroman.html
340 $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 );
341 break;
342 case 'ISO-8859-1':
343 $convmap = array( 256, 10000, 0, 0xffff );
344 }
345
346 if ( is_array( $convmap ) ) {
347 $line = mb_encode_numericentity( $line, $convmap, self::$charset );
348 }
349
350 if ( self::$to_encoding !== self::$charset ) {
351 $line = iconv( self::$charset, self::$to_encoding . '//IGNORE', $line );
352 }
353
354 return self::escape_csv( $line );
355 }
356
357 /**
358 * Escape a " in a csv with another "
359 *
360 * @since 2.0
361 */
362 public static function escape_csv( $value ) {
363 if ( '=' === $value[0] ) {
364 // escape the = to prevent vulnerability
365 $value = "'" . $value;
366 }
367 $value = str_replace( '"', '""', $value );
368
369 return $value;
370 }
371 }
372