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

696 lines 20.7 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 /**
9 * @var string $separator
10 */
11 protected static $separator = ', ';
12
13 /**
14 * @var string $column_separator
15 */
16 protected static $column_separator = ',';
17
18 /**
19 * @var string $line_break
20 */
21 protected static $line_break = 'return';
22
23 /**
24 * @var string $charset
25 */
26 protected static $charset = 'UTF-8';
27
28 /**
29 * @var string $to_encoding
30 */
31 protected static $to_encoding = 'UTF-8';
32
33 /**
34 * @var string $wp_date_format
35 */
36 protected static $wp_date_format = 'Y-m-d H:i:s';
37
38 /**
39 * @var int $comment_count
40 */
41 protected static $comment_count = 0;
42
43 /**
44 * @var int $form_id
45 */
46 protected static $form_id = 0;
47
48 /**
49 * @var array $headings
50 */
51 protected static $headings = array();
52
53 /**
54 * @var array $fields
55 */
56 protected static $fields = array();
57
58 /**
59 * @var stdClass|null $entry
60 */
61 protected static $entry;
62
63 /**
64 * @var bool|null $has_parent_id
65 */
66 protected static $has_parent_id;
67
68 /**
69 * @var array|null $fields_by_repeater_id
70 */
71 protected static $fields_by_repeater_id;
72
73 /**
74 * @var string $mode either 'echo' or 'file' are supported.
75 */
76 protected static $mode = 'echo';
77
78 /**
79 * @var resource|null $fp used to write a CSV file in file mode.
80 */
81 protected static $fp;
82
83 /**
84 * @var string $context the context of the CSV being generated. Possible values include 'email' when used as an email attachment, or 'default'.
85 */
86 protected static $context = 'default';
87
88 /**
89 * @var array $meta
90 */
91 protected static $meta = array();
92
93 public static function csv_format_options() {
94 $formats = array( 'UTF-8', 'ISO-8859-1', 'windows-1256', 'windows-1251', 'macintosh' );
95 $formats = apply_filters( 'frm_csv_format_options', $formats );
96
97 return $formats;
98 }
99
100 /**
101 * @param array $atts
102 * @return string|false|void returns a string file path or false if $atts['mode'] is set to 'file'.
103 */
104 public static function generate_csv( $atts ) {
105 global $frm_vars;
106 $frm_vars['prevent_caching'] = true;
107
108 self::$fields = $atts['form_cols'];
109 self::$form_id = $atts['form']->id;
110 self::$mode = ! empty( $atts['mode'] ) && 'file' === $atts['mode'] ? 'file' : 'echo';
111 self::$context = ! empty( $atts['context'] ) ? $atts['context'] : 'default';
112 self::$meta = ! empty( $atts['meta'] ) ? $atts['meta'] : array();
113
114 self::set_class_parameters();
115 self::set_has_parent_id( $atts['form'] );
116
117 $filename = self::generate_csv_filename( $atts['form'] );
118 unset( $atts['form'], $atts['form_cols'] );
119
120 if ( 'file' === self::$mode ) {
121 $filepath = get_temp_dir() . $filename;
122 self::$fp = @fopen( $filepath, 'w' );
123 if ( ! self::$fp ) {
124 return false;
125 }
126 } elseif ( 'echo' === self::$mode ) {
127 self::print_file_headers( $filename );
128 }
129
130 unset( $filename );
131
132 $comment_count = FrmDb::get_count(
133 'frm_item_metas',
134 array(
135 'item_id' => $atts['entry_ids'],
136 'field_id' => 0,
137 'meta_value like' => '{',
138 ),
139 array(
140 'group_by' => 'item_id',
141 'order_by' => 'count(*) DESC',
142 'limit' => 1,
143 )
144 );
145 self::$comment_count = $comment_count;
146
147 self::prepare_csv_headings();
148
149 // fetch 20 posts at a time rather than loading the entire table into memory
150 while ( $next_set = array_splice( $atts['entry_ids'], 0, 20 ) ) {
151 self::prepare_next_csv_rows( $next_set );
152 }
153
154 if ( 'file' === self::$mode ) {
155 fclose( self::$fp );
156 return $filepath;
157 }
158 }
159
160 /**
161 * @since 5.0.16
162 *
163 * @param stdClass $form
164 * @return string
165 */
166 private static function generate_csv_filename( $form ) {
167 $filename = gmdate( 'ymdHis', time() ) . '_' . sanitize_title_with_dashes( $form->name ) . '_formidable_entries.csv';
168 return apply_filters( 'frm_csv_filename', $filename, $form, self::get_standard_filter_args() );
169 }
170
171 /**
172 * @since 5.0.16
173 *
174 * @return array
175 */
176 private static function get_standard_filter_args() {
177 return array(
178 'context' => self::$context,
179 'meta' => self::$meta,
180 );
181 }
182
183 private static function set_class_parameters() {
184 $args = self::get_standard_filter_args();
185 self::$separator = apply_filters( 'frm_csv_sep', self::$separator, $args );
186 self::$line_break = apply_filters( 'frm_csv_line_break', self::$line_break, $args );
187 self::$wp_date_format = apply_filters( 'frm_csv_date_format', self::$wp_date_format, $args );
188 self::get_csv_format();
189 self::$charset = get_option( 'blog_charset' );
190
191 $col_sep = ! empty( $_POST['csv_col_sep'] ) ? sanitize_text_field( wp_unslash( $_POST['csv_col_sep'] ) ) : self::$column_separator; // phpcs:ignore WordPress.Security.NonceVerification.Missing
192
193 self::$column_separator = apply_filters( 'frm_csv_column_sep', $col_sep, $args );
194 }
195
196 private static function set_has_parent_id( $form ) {
197 self::$has_parent_id = $form->parent_form_id > 0;
198 }
199
200 private static function print_file_headers( $filename ) {
201 header( 'Content-Description: File Transfer' );
202 header( 'Content-Disposition: attachment; filename="' . esc_attr( $filename ) . '"' );
203 header( 'Content-Type: text/csv; charset=' . self::$charset, true );
204 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' );
205 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
206 header( 'Cache-Control: no-cache, must-revalidate' );
207 header( 'Pragma: no-cache' );
208
209 do_action(
210 'frm_csv_headers',
211 array(
212 'form_id' => self::$form_id,
213 'fields' => self::$fields,
214 )
215 );
216 }
217
218 public static function get_csv_format() {
219 $csv_format = FrmAppHelper::get_post_param( 'csv_format', 'UTF-8', 'sanitize_text_field' );
220 $csv_format = apply_filters( 'frm_csv_format', $csv_format, self::get_standard_filter_args() );
221 self::$to_encoding = $csv_format;
222 }
223
224 private static function prepare_csv_headings() {
225 $headings = array();
226 self::csv_headings( $headings );
227 $headings = apply_filters(
228 'frm_csv_columns',
229 $headings,
230 self::$form_id,
231 array_merge(
232 self::get_standard_filter_args(),
233 array( 'fields' => self::$fields )
234 )
235 );
236 self::$headings = $headings;
237
238 self::print_csv_row( $headings );
239 }
240
241 private static function field_headings( $col ) {
242 $field_type_obj = FrmFieldFactory::get_field_factory( $col );
243 if ( ! empty( $field_type_obj->is_combo_field ) ) { // This is combo field.
244 return $field_type_obj->get_export_headings();
245 }
246
247 $field_headings = array();
248 $separate_values = array( 'user_id', 'file', 'data', 'date' );
249 if ( ! empty( $col->field_options['separate_value'] ) && ! in_array( $col->type, $separate_values, true ) ) {
250 $field_headings[ $col->id . '_label' ] = strip_tags( $col->name . ' ' . __( '(label)', 'formidable' ) );
251 }
252
253 $field_headings[ $col->id ] = strip_tags( $col->name );
254 $field_headings = apply_filters(
255 'frm_csv_field_columns',
256 $field_headings,
257 array_merge(
258 self::get_standard_filter_args(),
259 array( 'field' => $col )
260 )
261 );
262
263 return $field_headings;
264 }
265
266 private static function csv_headings( &$headings ) {
267 $fields_by_repeater_id = array();
268 $repeater_ids = array();
269
270 foreach ( self::$fields as $col ) {
271 if ( self::is_the_child_of_a_repeater( $col ) ) {
272 $repeater_id = $col->field_options['in_section'];
273 $headings[ 'repeater' . $repeater_id ] = array(); // set a placeholder to maintain order for repeater fields
274
275 if ( ! isset( $fields_by_repeater_id[ $repeater_id ] ) ) {
276 $fields_by_repeater_id[ $repeater_id ] = array();
277 $repeater_ids[] = $repeater_id;
278 }
279
280 $fields_by_repeater_id[ $repeater_id ][] = $col;
281
282 continue;
283 }
284
285 $headings += self::field_headings( $col );
286 }
287 unset( $repeater_id, $col );
288
289 if ( $repeater_ids ) {
290 $where = array( 'field_id' => $repeater_ids );
291 $repeater_meta = FrmDb::get_results( 'frm_item_metas', $where, 'field_id, meta_value' );
292 $max = array_fill_keys( $repeater_ids, 0 );
293
294 foreach ( $repeater_meta as $row ) {
295 $start = strpos( $row->meta_value, 'a:' ) + 2;
296 $end = strpos( $row->meta_value, ':{' );
297 $length = substr( $row->meta_value, $start, $end - $start );
298
299 if ( $length > $max[ $row->field_id ] ) {
300 $max[ $row->field_id ] = $length;
301 }
302 }
303 unset( $start, $end, $length, $row, $repeater_meta, $where );
304
305 $flat = array();
306 foreach ( $headings as $key => $heading ) {
307 if ( is_array( $heading ) ) {
308 $repeater_id = str_replace( 'repeater', '', $key );
309
310 $repeater_headings = array();
311 foreach ( $fields_by_repeater_id[ $repeater_id ] as $col ) {
312 $repeater_headings += self::field_headings( $col );
313 }
314
315 for ( $i = 0; $i < $max[ $repeater_id ]; $i ++ ) {
316 foreach ( $repeater_headings as $repeater_key => $repeater_name ) {
317 $flat[ $repeater_key . '[' . $i . ']' ] = $repeater_name;
318 }
319 }
320 } else {
321 $flat[ $key ] = $heading;
322 }
323 }
324
325 self::$fields_by_repeater_id = $fields_by_repeater_id;
326
327 unset( $key, $heading, $max, $repeater_headings, $repeater_id, $fields_by_repeater_id );
328
329 $headings = $flat;
330 unset( $flat );
331 }
332
333 if ( self::$comment_count ) {
334 for ( $i = 0; $i < self::$comment_count; $i ++ ) {
335 $headings[ 'comment' . $i ] = __( 'Comment', 'formidable' );
336 $headings[ 'comment_user_id' . $i ] = __( 'Comment User', 'formidable' );
337 $headings[ 'comment_created_at' . $i ] = __( 'Comment Date', 'formidable' );
338 }
339 unset( $i );
340 }
341
342 $headings['created_at'] = __( 'Timestamp', 'formidable' );
343 $headings['updated_at'] = __( 'Last Updated', 'formidable' );
344 $headings['user_id'] = __( 'Created By', 'formidable' );
345 $headings['updated_by'] = __( 'Updated By', 'formidable' );
346 $headings['is_draft'] = __( 'Draft', 'formidable' );
347 $headings['ip'] = __( 'IP', 'formidable' );
348 $headings['id'] = __( 'ID', 'formidable' );
349 $headings['item_key'] = __( 'Key', 'formidable' );
350 if ( self::has_parent_id() ) {
351 $headings['parent_id'] = __( 'Parent ID', 'formidable' );
352 }
353
354 $headings = apply_filters( 'frm_export_csv_headings', $headings );
355 }
356
357 /**
358 * @param object $field
359 * @return bool
360 */
361 private static function is_the_child_of_a_repeater( $field ) {
362 if ( $field->form_id === self::$form_id || empty( $field->field_options['in_section'] ) ) {
363 return false;
364 }
365
366 $section_id = $field->field_options['in_section'];
367 $section = FrmField::getOne( $section_id );
368
369 if ( ! $section ) {
370 return false;
371 }
372
373 return FrmField::is_repeating_field( $section );
374 }
375
376 private static function has_parent_id() {
377 return self::$has_parent_id;
378 }
379
380 private static function prepare_next_csv_rows( $next_set ) {
381 // order by parent_item_id so children will be first
382 $where = array(
383 'or' => 1,
384 'id' => $next_set,
385 'parent_item_id' => $next_set,
386 );
387 $entries = FrmEntry::getAll( $where, ' ORDER BY parent_item_id DESC', '', true, false );
388
389 foreach ( $entries as $k => $entry ) {
390 self::$entry = $entry;
391 unset( $entry );
392
393 if ( self::$entry->form_id !== self::$form_id ) {
394 self::add_repeat_field_values_to_csv( $entries );
395 } else {
396 self::prepare_csv_row();
397 }
398 }
399 }
400
401 private static function prepare_csv_row() {
402 $row = array();
403 self::add_field_values_to_csv( $row );
404 self::add_entry_data_to_csv( $row );
405 $row = apply_filters(
406 'frm_csv_row',
407 $row,
408 array(
409 'entry' => self::$entry,
410 'date_format' => self::$wp_date_format,
411 'comment_count' => self::$comment_count,
412 'context' => self::$context,
413 )
414 );
415 self::print_csv_row( $row );
416 }
417
418 private static function add_repeat_field_values_to_csv( &$entries ) {
419 if ( isset( self::$entry->metas ) ) {
420 // add child entries to the parent
421 foreach ( self::$entry->metas as $meta_id => $meta_value ) {
422 if ( ! is_numeric( $meta_id ) || '' === $meta_value ) {
423 // if the hook is being used to include field keys in the metas array,
424 // we need to skip the keys and only process field ids
425 continue;
426 }
427
428 if ( ! isset( $entries[ self::$entry->parent_item_id ] ) ) {
429 $entries[ self::$entry->parent_item_id ] = new stdClass();
430 $entries[ self::$entry->parent_item_id ]->metas = array();
431 }
432
433 if ( ! isset( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
434 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = array();
435 } elseif ( ! is_array( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
436 // if the data is here, it should be an array but if this field has collected data
437 // both while inside and outside of the repeating section, it's possible this is a string
438 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = (array) $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ];
439 }
440
441 //add the repeated values
442 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ][] = $meta_value;
443 }
444
445 self::$entry->metas = self::fill_missing_repeater_metas( self::$entry->metas, $entries );
446 $entries[ self::$entry->parent_item_id ]->metas += self::$entry->metas;
447 }
448
449 // add the embedded form id
450 if ( ! isset( $entries[ self::$entry->parent_item_id ]->embedded_fields ) ) {
451 $entries[ self::$entry->parent_item_id ]->embedded_fields = array();
452 }
453 $entries[ self::$entry->parent_item_id ]->embedded_fields[ self::$entry->id ] = self::$entry->form_id;
454 }
455
456 /**
457 * When an empty field is saved, it isn't saved as a meta value
458 * The export needs all of the meta to be filled in, so we put blank strings for every missing repeater child
459 *
460 * @param array $metas
461 * @param array $entries
462 * @return array
463 */
464 private static function fill_missing_repeater_metas( $metas, &$entries ) {
465 $field_ids = array_keys( $metas );
466 $field_id = end( $field_ids );
467 $field = self::get_field( $field_id );
468
469 if ( ! $field || empty( $field->field_options['in_section'] ) ) {
470 return $metas;
471 }
472
473 $repeater_id = $field->field_options['in_section'];
474 if ( ! isset( self::$fields_by_repeater_id[ $repeater_id ] ) ) {
475 return $metas;
476 }
477
478 foreach ( self::$fields_by_repeater_id[ $repeater_id ] as $repeater_child ) {
479 if ( ! isset( $metas[ $repeater_child->id ] ) ) {
480 $metas[ $repeater_child->id ] = '';
481 $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ][] = '';
482 }
483 }
484
485 return $metas;
486 }
487
488 private static function get_field( $field_id ) {
489 $field_id = (int) $field_id;
490 foreach ( self::$fields as $field ) {
491 if ( $field_id === (int) $field->id ) {
492 return $field;
493 }
494 }
495 return false;
496 }
497
498 private static function add_field_values_to_csv( &$row ) {
499 foreach ( self::$fields as $col ) {
500 $field_value = isset( self::$entry->metas[ $col->id ] ) ? self::$entry->metas[ $col->id ] : false;
501
502 FrmAppHelper::unserialize_or_decode( $field_value );
503 self::add_array_values_to_columns( $row, compact( 'col', 'field_value' ) );
504
505 $field_value = apply_filters(
506 'frm_csv_value',
507 $field_value,
508 array(
509 'field' => $col,
510 'entry' => self::$entry,
511 'separator' => self::$separator,
512 'context' => self::$context,
513 )
514 );
515
516 if ( ! empty( $col->field_options['separate_value'] ) ) {
517 $label_key = $col->id . '_label';
518 if ( self::is_the_child_of_a_repeater( $col ) ) {
519 $row[ $label_key ] = array();
520 foreach ( $field_value as $value ) {
521 $row[ $label_key ][] = self::get_separate_value_label( $value, $col );
522 }
523 } else {
524 $row[ $label_key ] = self::get_separate_value_label( $field_value, $col );
525 }
526 unset( $label_key );
527 }
528
529 $row[ $col->id ] = $field_value;
530
531 unset( $col, $field_value );
532 }
533 }
534
535 /**
536 * @since 5.0.06
537 *
538 * @param mixed $field_value
539 * @param stdClass $field
540 * @return string
541 */
542 private static function get_separate_value_label( $field_value, $field ) {
543 return FrmEntriesHelper::display_value(
544 $field_value,
545 $field,
546 array(
547 'type' => $field->type,
548 'post_id' => self::$entry->post_id,
549 'show_icon' => false,
550 'entry_id' => self::$entry->id,
551 'sep' => self::$separator,
552 '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,
553 )
554 );
555 }
556
557 /**
558 * @since 2.0.23
559 */
560 private static function add_array_values_to_columns( &$row, $atts ) {
561 if ( is_array( $atts['field_value'] ) ) {
562 foreach ( $atts['field_value'] as $key => $sub_value ) {
563 if ( is_array( $sub_value ) ) {
564 // This is combo field inside repeater. The heading key has this format: [86_first[0]].
565 foreach ( $sub_value as $sub_key => $sub_sub_value ) {
566 $column_key = $atts['col']->id . '_' . $sub_key . '[' . $key . ']';
567 if ( ! is_numeric( $sub_key ) && isset( self::$headings[ $column_key ] ) ) {
568 $row[ $column_key ] = $sub_sub_value;
569 }
570 }
571
572 continue;
573 }
574
575 $column_key = $atts['col']->id . '_' . $key;
576 if ( ! is_numeric( $key ) && isset( self::$headings[ $column_key ] ) ) {
577 $row[ $column_key ] = $sub_value;
578 }
579 }
580 }
581 }
582
583 private static function add_entry_data_to_csv( &$row ) {
584 $row['created_at'] = FrmAppHelper::get_formatted_time( self::$entry->created_at, self::$wp_date_format, ' ' );
585 $row['updated_at'] = FrmAppHelper::get_formatted_time( self::$entry->updated_at, self::$wp_date_format, ' ' );
586 $row['user_id'] = self::$entry->user_id;
587 $row['updated_by'] = self::$entry->updated_by;
588 $row['is_draft'] = self::$entry->is_draft ? '1' : '0';
589 $row['ip'] = self::$entry->ip;
590 $row['id'] = self::$entry->id;
591 $row['item_key'] = self::$entry->item_key;
592 if ( self::has_parent_id() ) {
593 $row['parent_id'] = self::$entry->parent_item_id;
594 }
595 }
596
597 private static function print_csv_row( $rows ) {
598 $sep = '';
599 $echo = 'echo' === self::$mode;
600
601 foreach ( self::$headings as $k => $heading ) {
602 if ( isset( $rows[ $k ] ) ) {
603 $row = $rows[ $k ];
604 } else {
605 $row = '';
606 // array indexed data is not at $rows[ $k ]
607 if ( $k[ strlen( $k ) - 1 ] === ']' ) {
608 $start = strrpos( $k, '[' );
609 $key = substr( $k, 0, $start ++ );
610 $index = substr( $k, $start, strlen( $k ) - 1 - $start );
611
612 if ( isset( $rows[ $key ] ) && isset( $rows[ $key ][ $index ] ) ) {
613 $row = $rows[ $key ][ $index ];
614 }
615
616 unset( $start, $key, $index );
617 }
618 }
619
620 if ( is_array( $row ) ) {
621 // implode the repeated field values
622 $row = implode( self::$separator, FrmAppHelper::array_flatten( $row, 'reset' ) );
623 }
624
625 $val = self::encode_value( $row );
626 if ( 'return' !== self::$line_break ) {
627 $val = str_replace( array( "\r\n", "\r", "\n" ), self::$line_break, $val );
628 }
629
630 if ( $echo ) {
631 echo $sep . '"' . $val . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
632 } else {
633 fwrite( self::$fp, $sep . '"' . $val . '"' );
634 }
635 $sep = self::$column_separator;
636
637 unset( $k, $row );
638 }
639 if ( $echo ) {
640 echo "\n";
641 } else {
642 fwrite( self::$fp, "\n" );
643 }
644 }
645
646 public static function encode_value( $line ) {
647 if ( '' === $line ) {
648 return $line;
649 }
650
651 $convmap = false;
652
653 switch ( self::$to_encoding ) {
654 case 'macintosh':
655 // this map was derived from the differences between the MacRoman and UTF-8 Charsets
656 // Reference:
657 // - http://www.alanwood.net/demos/macroman.html
658 $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 );
659 break;
660 case 'ISO-8859-1':
661 $convmap = array( 256, 10000, 0, 0xffff );
662 }
663
664 if ( is_array( $convmap ) ) {
665 $line = mb_encode_numericentity( $line, $convmap, self::$charset );
666 }
667
668 if ( self::$to_encoding !== self::$charset ) {
669 $line = iconv( self::$charset, self::$to_encoding . '//IGNORE', $line );
670 }
671
672 return self::escape_csv( $line );
673 }
674
675 /**
676 * Escape a " in a csv with another "
677 *
678 * @since 2.0
679 * @param mixed $value
680 * @return mixed
681 */
682 public static function escape_csv( $value ) {
683 if ( ! is_string( $value ) ) {
684 return $value;
685 }
686
687 if ( '=' === $value[0] ) {
688 // escape the = to prevent vulnerability
689 $value = "'" . $value;
690 }
691 $value = str_replace( '"', '""', $value );
692
693 return $value;
694 }
695 }
696