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

704 lines 20.9 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
482 if ( ! isset( $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ] ) || ! is_array( $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ] ) ) {
483 $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ] = array();
484 }
485
486 $entries[ self::$entry->parent_item_id ]->metas[ $repeater_child->id ][] = '';
487 }
488 }
489
490 return $metas;
491 }
492
493 private static function get_field( $field_id ) {
494 $field_id = (int) $field_id;
495 foreach ( self::$fields as $field ) {
496 if ( $field_id === (int) $field->id ) {
497 return $field;
498 }
499 }
500 return false;
501 }
502
503 private static function add_field_values_to_csv( &$row ) {
504 foreach ( self::$fields as $col ) {
505 $field_value = isset( self::$entry->metas[ $col->id ] ) ? self::$entry->metas[ $col->id ] : false;
506
507 FrmAppHelper::unserialize_or_decode( $field_value );
508 self::add_array_values_to_columns( $row, compact( 'col', 'field_value' ) );
509
510 $field_value = apply_filters(
511 'frm_csv_value',
512 $field_value,
513 array(
514 'field' => $col,
515 'entry' => self::$entry,
516 'separator' => self::$separator,
517 'context' => self::$context,
518 )
519 );
520
521 if ( ! empty( $col->field_options['separate_value'] ) ) {
522 $label_key = $col->id . '_label';
523 if ( self::is_the_child_of_a_repeater( $col ) ) {
524 $row[ $label_key ] = array();
525
526 if ( is_array( $field_value ) ) {
527 foreach ( $field_value as $value ) {
528 $row[ $label_key ][] = self::get_separate_value_label( $value, $col );
529 }
530 }
531 } else {
532 $row[ $label_key ] = self::get_separate_value_label( $field_value, $col );
533 }
534 unset( $label_key );
535 }
536
537 $row[ $col->id ] = $field_value;
538
539 unset( $col, $field_value );
540 }
541 }
542
543 /**
544 * @since 5.0.06
545 *
546 * @param mixed $field_value
547 * @param stdClass $field
548 * @return string
549 */
550 private static function get_separate_value_label( $field_value, $field ) {
551 return FrmEntriesHelper::display_value(
552 $field_value,
553 $field,
554 array(
555 'type' => $field->type,
556 'post_id' => self::$entry->post_id,
557 'show_icon' => false,
558 'entry_id' => self::$entry->id,
559 'sep' => self::$separator,
560 '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,
561 )
562 );
563 }
564
565 /**
566 * @since 2.0.23
567 */
568 private static function add_array_values_to_columns( &$row, $atts ) {
569 if ( is_array( $atts['field_value'] ) ) {
570 foreach ( $atts['field_value'] as $key => $sub_value ) {
571 if ( is_array( $sub_value ) ) {
572 // This is combo field inside repeater. The heading key has this format: [86_first[0]].
573 foreach ( $sub_value as $sub_key => $sub_sub_value ) {
574 $column_key = $atts['col']->id . '_' . $sub_key . '[' . $key . ']';
575 if ( ! is_numeric( $sub_key ) && isset( self::$headings[ $column_key ] ) ) {
576 $row[ $column_key ] = $sub_sub_value;
577 }
578 }
579
580 continue;
581 }
582
583 $column_key = $atts['col']->id . '_' . $key;
584 if ( ! is_numeric( $key ) && isset( self::$headings[ $column_key ] ) ) {
585 $row[ $column_key ] = $sub_value;
586 }
587 }
588 }
589 }
590
591 private static function add_entry_data_to_csv( &$row ) {
592 $row['created_at'] = FrmAppHelper::get_formatted_time( self::$entry->created_at, self::$wp_date_format, ' ' );
593 $row['updated_at'] = FrmAppHelper::get_formatted_time( self::$entry->updated_at, self::$wp_date_format, ' ' );
594 $row['user_id'] = self::$entry->user_id;
595 $row['updated_by'] = self::$entry->updated_by;
596 $row['is_draft'] = self::$entry->is_draft ? '1' : '0';
597 $row['ip'] = self::$entry->ip;
598 $row['id'] = self::$entry->id;
599 $row['item_key'] = self::$entry->item_key;
600 if ( self::has_parent_id() ) {
601 $row['parent_id'] = self::$entry->parent_item_id;
602 }
603 }
604
605 private static function print_csv_row( $rows ) {
606 $sep = '';
607 $echo = 'echo' === self::$mode;
608
609 foreach ( self::$headings as $k => $heading ) {
610 if ( isset( $rows[ $k ] ) ) {
611 $row = $rows[ $k ];
612 } else {
613 $row = '';
614 // array indexed data is not at $rows[ $k ]
615 if ( $k[ strlen( $k ) - 1 ] === ']' ) {
616 $start = strrpos( $k, '[' );
617 $key = substr( $k, 0, $start ++ );
618 $index = substr( $k, $start, strlen( $k ) - 1 - $start );
619
620 if ( isset( $rows[ $key ] ) && isset( $rows[ $key ][ $index ] ) ) {
621 $row = $rows[ $key ][ $index ];
622 }
623
624 unset( $start, $key, $index );
625 }
626 }
627
628 if ( is_array( $row ) ) {
629 // implode the repeated field values
630 $row = implode( self::$separator, FrmAppHelper::array_flatten( $row, 'reset' ) );
631 }
632
633 $val = self::encode_value( $row );
634 if ( 'return' !== self::$line_break ) {
635 $val = str_replace( array( "\r\n", "\r", "\n" ), self::$line_break, $val );
636 }
637
638 if ( $echo ) {
639 echo $sep . '"' . $val . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
640 } else {
641 fwrite( self::$fp, $sep . '"' . $val . '"' );
642 }
643 $sep = self::$column_separator;
644
645 unset( $k, $row );
646 }
647 if ( $echo ) {
648 echo "\n";
649 } else {
650 fwrite( self::$fp, "\n" );
651 }
652 }
653
654 public static function encode_value( $line ) {
655 if ( '' === $line ) {
656 return $line;
657 }
658
659 $convmap = false;
660
661 switch ( self::$to_encoding ) {
662 case 'macintosh':
663 // this map was derived from the differences between the MacRoman and UTF-8 Charsets
664 // Reference:
665 // - http://www.alanwood.net/demos/macroman.html
666 $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 );
667 break;
668 case 'ISO-8859-1':
669 $convmap = array( 256, 10000, 0, 0xffff );
670 }
671
672 if ( is_array( $convmap ) ) {
673 $line = mb_encode_numericentity( $line, $convmap, self::$charset );
674 }
675
676 if ( self::$to_encoding !== self::$charset ) {
677 $line = iconv( self::$charset, self::$to_encoding . '//IGNORE', $line );
678 }
679
680 return self::escape_csv( $line );
681 }
682
683 /**
684 * Escape a " in a csv with another "
685 *
686 * @since 2.0
687 * @param mixed $value
688 * @return mixed
689 */
690 public static function escape_csv( $value ) {
691 if ( ! is_string( $value ) ) {
692 return $value;
693 }
694
695 if ( '=' === $value[0] ) {
696 // escape the = to prevent vulnerability
697 $value = "'" . $value;
698 }
699 $value = str_replace( '"', '""', $value );
700
701 return $value;
702 }
703 }
704