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

706 lines 21.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 /**
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 ) ) {
244 // This is combo field.
245 return $field_type_obj->get_export_headings();
246 }
247
248 $field_headings = array();
249 $separate_values = array( 'user_id', 'file', 'data', 'date' );
250 if ( ! empty( $col->field_options['separate_value'] ) && ! in_array( $col->type, $separate_values, true ) ) {
251 $field_headings[ $col->id . '_label' ] = strip_tags( $col->name . ' ' . __( '(label)', 'formidable' ) );
252 }
253
254 $field_headings[ $col->id ] = strip_tags( $col->name );
255 $field_headings = apply_filters(
256 'frm_csv_field_columns',
257 $field_headings,
258 array_merge(
259 self::get_standard_filter_args(),
260 array( 'field' => $col )
261 )
262 );
263
264 return $field_headings;
265 }
266
267 private static function csv_headings( &$headings ) {
268 $fields_by_repeater_id = array();
269 $repeater_ids = array();
270
271 foreach ( self::$fields as $col ) {
272 if ( self::is_the_child_of_a_repeater( $col ) ) {
273 $repeater_id = $col->field_options['in_section'];
274 // Set a placeholder to maintain order for repeater fields.
275 $headings[ 'repeater' . $repeater_id ] = array();
276
277 if ( ! isset( $fields_by_repeater_id[ $repeater_id ] ) ) {
278 $fields_by_repeater_id[ $repeater_id ] = array();
279 $repeater_ids[] = $repeater_id;
280 }
281
282 $fields_by_repeater_id[ $repeater_id ][] = $col;
283
284 continue;
285 }
286
287 $headings += self::field_headings( $col );
288 }
289 unset( $repeater_id, $col );
290
291 if ( $repeater_ids ) {
292 $where = array( 'field_id' => $repeater_ids );
293 $repeater_meta = FrmDb::get_results( 'frm_item_metas', $where, 'field_id, meta_value' );
294 $max = array_fill_keys( $repeater_ids, 0 );
295
296 foreach ( $repeater_meta as $row ) {
297 $start = strpos( $row->meta_value, 'a:' ) + 2;
298 $end = strpos( $row->meta_value, ':{' );
299 $length = substr( $row->meta_value, $start, $end - $start );
300
301 if ( $length > $max[ $row->field_id ] ) {
302 $max[ $row->field_id ] = $length;
303 }
304 }
305 unset( $start, $end, $length, $row, $repeater_meta, $where );
306
307 $flat = array();
308 foreach ( $headings as $key => $heading ) {
309 if ( is_array( $heading ) ) {
310 $repeater_id = str_replace( 'repeater', '', $key );
311
312 $repeater_headings = array();
313 foreach ( $fields_by_repeater_id[ $repeater_id ] as $col ) {
314 $repeater_headings += self::field_headings( $col );
315 }
316
317 for ( $i = 0; $i < $max[ $repeater_id ]; $i ++ ) {
318 foreach ( $repeater_headings as $repeater_key => $repeater_name ) {
319 $flat[ $repeater_key . '[' . $i . ']' ] = $repeater_name;
320 }
321 }
322 } else {
323 $flat[ $key ] = $heading;
324 }
325 }
326
327 self::$fields_by_repeater_id = $fields_by_repeater_id;
328
329 unset( $key, $heading, $max, $repeater_headings, $repeater_id, $fields_by_repeater_id );
330
331 $headings = $flat;
332 unset( $flat );
333 }//end if
334
335 if ( self::$comment_count ) {
336 for ( $i = 0; $i < self::$comment_count; $i ++ ) {
337 $headings[ 'comment' . $i ] = __( 'Comment', 'formidable' );
338 $headings[ 'comment_user_id' . $i ] = __( 'Comment User', 'formidable' );
339 $headings[ 'comment_created_at' . $i ] = __( 'Comment Date', 'formidable' );
340 }
341 unset( $i );
342 }
343
344 $headings['created_at'] = __( 'Timestamp', 'formidable' );
345 $headings['updated_at'] = __( 'Last Updated', 'formidable' );
346 $headings['user_id'] = __( 'Created By', 'formidable' );
347 $headings['updated_by'] = __( 'Updated By', 'formidable' );
348 $headings['is_draft'] = __( 'Entry Status', 'formidable' );
349 $headings['ip'] = __( 'IP', 'formidable' );
350 $headings['id'] = __( 'ID', 'formidable' );
351 $headings['item_key'] = __( 'Key', 'formidable' );
352 if ( self::has_parent_id() ) {
353 $headings['parent_id'] = __( 'Parent ID', 'formidable' );
354 }
355
356 $headings = apply_filters( 'frm_export_csv_headings', $headings );
357 }
358
359 /**
360 * @param object $field
361 * @return bool
362 */
363 private static function is_the_child_of_a_repeater( $field ) {
364 if ( $field->form_id === self::$form_id || empty( $field->field_options['in_section'] ) ) {
365 return false;
366 }
367
368 $section_id = $field->field_options['in_section'];
369 $section = FrmField::getOne( $section_id );
370
371 if ( ! $section ) {
372 return false;
373 }
374
375 return FrmField::is_repeating_field( $section );
376 }
377
378 private static function has_parent_id() {
379 return self::$has_parent_id;
380 }
381
382 private static function prepare_next_csv_rows( $next_set ) {
383 // order by parent_item_id so children will be first
384 $where = array(
385 'or' => 1,
386 'id' => $next_set,
387 'parent_item_id' => $next_set,
388 );
389 $entries = FrmEntry::getAll( $where, ' ORDER BY parent_item_id DESC', '', true, false );
390
391 foreach ( $entries as $k => $entry ) {
392 self::$entry = $entry;
393 unset( $entry );
394
395 if ( self::$entry->form_id !== self::$form_id ) {
396 self::add_repeat_field_values_to_csv( $entries );
397 } else {
398 self::prepare_csv_row();
399 }
400 }
401 }
402
403 private static function prepare_csv_row() {
404 $row = array();
405 self::add_field_values_to_csv( $row );
406 self::add_entry_data_to_csv( $row );
407 $row = apply_filters(
408 'frm_csv_row',
409 $row,
410 array(
411 'entry' => self::$entry,
412 'date_format' => self::$wp_date_format,
413 'comment_count' => self::$comment_count,
414 'context' => self::$context,
415 )
416 );
417 self::print_csv_row( $row );
418 }
419
420 private static function add_repeat_field_values_to_csv( &$entries ) {
421 if ( isset( self::$entry->metas ) ) {
422 // add child entries to the parent
423 foreach ( self::$entry->metas as $meta_id => $meta_value ) {
424 if ( ! is_numeric( $meta_id ) || '' === $meta_value ) {
425 // if the hook is being used to include field keys in the metas array,
426 // we need to skip the keys and only process field ids
427 continue;
428 }
429
430 if ( ! isset( $entries[ self::$entry->parent_item_id ] ) ) {
431 $entries[ self::$entry->parent_item_id ] = new stdClass();
432 $entries[ self::$entry->parent_item_id ]->metas = array();
433 }
434
435 if ( ! isset( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
436 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = array();
437 } elseif ( ! is_array( $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] ) ) {
438 // if the data is here, it should be an array but if this field has collected data
439 // both while inside and outside of the repeating section, it's possible this is a string.
440 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ] = (array) $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ];
441 }
442
443 // Add the repeated values.
444 $entries[ self::$entry->parent_item_id ]->metas[ $meta_id ][] = $meta_value;
445 }//end foreach
446
447 self::$entry->metas = self::fill_missing_repeater_metas( self::$entry->metas, $entries );
448 $entries[ self::$entry->parent_item_id ]->metas += self::$entry->metas;
449 }//end if
450
451 // add the embedded form id
452 if ( ! isset( $entries[ self::$entry->parent_item_id ]->embedded_fields ) ) {
453 $entries[ self::$entry->parent_item_id ]->embedded_fields = array();
454 }
455 $entries[ self::$entry->parent_item_id ]->embedded_fields[ self::$entry->id ] = self::$entry->form_id;
456 }
457
458 /**
459 * When an empty field is saved, it isn't saved as a meta value
460 * The export needs all of the meta to be filled in, so we put blank strings for every missing repeater child
461 *
462 * @param array $metas
463 * @param array $entries
464 * @return array
465 */
466 private static function fill_missing_repeater_metas( $metas, &$entries ) {
467 $field_ids = array_keys( $metas );
468 $field_id = end( $field_ids );
469 $field = self::get_field( $field_id );
470
471 if ( ! $field || empty( $field->field_options['in_section'] ) ) {
472 return $metas;
473 }
474
475 $repeater_id = $field->field_options['in_section'];
476 if ( ! isset( self::$fields_by_repeater_id[ $repeater_id ] ) ) {
477 return $metas;
478 }
479
480 foreach ( self::$fields_by_repeater_id[ $repeater_id ] as $repeater_child ) {
481 if ( ! isset( $metas[ $repeater_child->id ] ) ) {
482 $metas[ $repeater_child->id ] = '';
483
484 if ( ! isset( $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ] ) || ! is_array( $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ] ) ) {
485 $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ] = array();
486 }
487
488 $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ][] = '';
489 }
490 }
491
492 return $metas;
493 }
494
495 private static function get_field( $field_id ) {
496 $field_id = (int) $field_id;
497 foreach ( self::$fields as $field ) {
498 if ( $field_id === (int) $field->id ) {
499 return $field;
500 }
501 }
502 return false;
503 }
504
505 private static function add_field_values_to_csv( &$row ) {
506 foreach ( self::$fields as $col ) {
507 $field_value = isset( self::$entry->metas[ $col->id ] ) ? self::$entry->metas[ $col->id ] : false;
508
509 FrmFieldsHelper::prepare_field_value( $field_value, $col->type );
510 self::add_array_values_to_columns( $row, compact( 'col', 'field_value' ) );
511
512 $field_value = apply_filters(
513 'frm_csv_value',
514 $field_value,
515 array(
516 'field' => $col,
517 'entry' => self::$entry,
518 'separator' => self::$separator,
519 'context' => self::$context,
520 )
521 );
522
523 if ( ! empty( $col->field_options['separate_value'] ) ) {
524 $label_key = $col->id . '_label';
525 if ( self::is_the_child_of_a_repeater( $col ) ) {
526 $row[ $label_key ] = array();
527
528 if ( is_array( $field_value ) ) {
529 foreach ( $field_value as $value ) {
530 $row[ $label_key ][] = self::get_separate_value_label( $value, $col );
531 }
532 }
533 } else {
534 $row[ $label_key ] = self::get_separate_value_label( $field_value, $col );
535 }
536 unset( $label_key );
537 }
538
539 $row[ $col->id ] = $field_value;
540
541 unset( $col, $field_value );
542 }//end foreach
543 }
544
545 /**
546 * @since 5.0.06
547 *
548 * @param mixed $field_value
549 * @param stdClass $field
550 * @return string
551 */
552 private static function get_separate_value_label( $field_value, $field ) {
553 return FrmEntriesHelper::display_value(
554 $field_value,
555 $field,
556 array(
557 'type' => $field->type,
558 'post_id' => self::$entry->post_id,
559 'show_icon' => false,
560 'entry_id' => self::$entry->id,
561 'sep' => self::$separator,
562 '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,
563 )
564 );
565 }
566
567 /**
568 * @since 2.0.23
569 */
570 private static function add_array_values_to_columns( &$row, $atts ) {
571 if ( is_array( $atts['field_value'] ) ) {
572 foreach ( $atts['field_value'] as $key => $sub_value ) {
573 if ( is_array( $sub_value ) ) {
574 // This is combo field inside repeater. The heading key has this format: [86_first[0]].
575 foreach ( $sub_value as $sub_key => $sub_sub_value ) {
576 $column_key = $atts['col']->id . '_' . $sub_key . '[' . $key . ']';
577 if ( ! is_numeric( $sub_key ) && isset( self::$headings[ $column_key ] ) ) {
578 $row[ $column_key ] = $sub_sub_value;
579 }
580 }
581
582 continue;
583 }
584
585 $column_key = $atts['col']->id . '_' . $key;
586 if ( ! is_numeric( $key ) && isset( self::$headings[ $column_key ] ) ) {
587 $row[ $column_key ] = $sub_value;
588 }
589 }
590 }
591 }
592
593 private static function add_entry_data_to_csv( &$row ) {
594 $row['created_at'] = FrmAppHelper::get_formatted_time( self::$entry->created_at, self::$wp_date_format, ' ' );
595 $row['updated_at'] = FrmAppHelper::get_formatted_time( self::$entry->updated_at, self::$wp_date_format, ' ' );
596 $row['user_id'] = self::$entry->user_id;
597 $row['updated_by'] = self::$entry->updated_by;
598 $row['is_draft'] = self::$entry->is_draft;
599 $row['ip'] = self::$entry->ip;
600 $row['id'] = self::$entry->id;
601 $row['item_key'] = self::$entry->item_key;
602 if ( self::has_parent_id() ) {
603 $row['parent_id'] = self::$entry->parent_item_id;
604 }
605 }
606
607 private static function print_csv_row( $rows ) {
608 $sep = '';
609 $echo = 'echo' === self::$mode;
610
611 foreach ( self::$headings as $k => $heading ) {
612 if ( isset( $rows[ $k ] ) ) {
613 $row = $rows[ $k ];
614 } else {
615 $row = '';
616 // array indexed data is not at $rows[ $k ]
617 if ( $k[ strlen( $k ) - 1 ] === ']' ) {
618 $start = strrpos( $k, '[' );
619 $key = substr( $k, 0, $start ++ );
620 $index = substr( $k, $start, strlen( $k ) - 1 - $start );
621
622 if ( isset( $rows[ $key ] ) && isset( $rows[ $key ][ $index ] ) ) {
623 $row = $rows[ $key ][ $index ];
624 }
625
626 unset( $start, $key, $index );
627 }
628 }
629
630 if ( is_array( $row ) ) {
631 // implode the repeated field values
632 $row = implode( self::$separator, FrmAppHelper::array_flatten( $row, 'reset' ) );
633 }
634
635 $val = self::encode_value( $row );
636 if ( 'return' !== self::$line_break ) {
637 $val = str_replace( array( "\r\n", "\r", "\n" ), self::$line_break, $val );
638 }
639
640 if ( $echo ) {
641 echo $sep . '"' . $val . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
642 } else {
643 fwrite( self::$fp, $sep . '"' . $val . '"' );
644 }
645 $sep = self::$column_separator;
646
647 unset( $k, $row );
648 }//end foreach
649 if ( $echo ) {
650 echo "\n";
651 } else {
652 fwrite( self::$fp, "\n" );
653 }
654 }
655
656 public static function encode_value( $line ) {
657 if ( '' === $line ) {
658 return $line;
659 }
660
661 $convmap = false;
662
663 switch ( self::$to_encoding ) {
664 case 'macintosh':
665 // this map was derived from the differences between the MacRoman and UTF-8 Charsets
666 // Reference:
667 // http://www.alanwood.net/demos/macroman.html.
668 $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 );
669 break;
670 case 'ISO-8859-1':
671 $convmap = array( 256, 10000, 0, 0xffff );
672 }
673
674 if ( is_array( $convmap ) ) {
675 $line = mb_encode_numericentity( $line, $convmap, self::$charset );
676 }
677
678 if ( self::$to_encoding !== self::$charset ) {
679 $line = iconv( self::$charset, self::$to_encoding . '//IGNORE', $line );
680 }
681
682 return self::escape_csv( $line );
683 }
684
685 /**
686 * Escape a " in a csv with another "
687 *
688 * @since 2.0
689 * @param mixed $value
690 * @return mixed
691 */
692 public static function escape_csv( $value ) {
693 if ( ! is_string( $value ) ) {
694 return $value;
695 }
696
697 if ( '=' === $value[0] ) {
698 // escape the = to prevent vulnerability
699 $value = "'" . $value;
700 }
701 $value = str_replace( '"', '""', $value );
702
703 return $value;
704 }
705 }
706