PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.6
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.6
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 / FrmXMLHelper.php

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

2,057 lines 61.8 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 FrmXMLHelper {
7
8 /**
9 * @var bool $installing_template true if importing an XML from API, false if importing an XML file manually.
10 */
11 private static $installing_template = false;
12
13 public static function get_xml_values( $opt, $padding ) {
14 if ( is_array( $opt ) ) {
15 foreach ( $opt as $ok => $ov ) {
16 echo "\n" . esc_html( $padding );
17 $tag = ( is_numeric( $ok ) ? 'key:' : '' ) . $ok;
18 echo '<' . esc_html( $tag ) . '>';
19 self::get_xml_values( $ov, $padding . ' ' );
20 if ( is_array( $ov ) ) {
21 echo "\n" . esc_html( $padding );
22 }
23 echo '</' . esc_html( $tag ) . '>';
24 }
25 } else {
26 echo self::cdata( $opt ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
27 }
28 }
29
30 public static function import_xml( $file ) {
31 if ( ! defined( 'WP_IMPORTING' ) ) {
32 define( 'WP_IMPORTING', true );
33 }
34
35 if ( ! class_exists( 'DOMDocument' ) ) {
36 $error_message = sprintf(
37 /* translators: 1: Documentation link */
38 __( 'In order to install XML, your server must have DOMDocument installed. Follow our documentation on %1$s to ensure DOMDocument is properly set up and XML support is enabled.', 'formidable' ),
39 '<a href="https://formidableforms.com/knowledgebase/import-forms-entries-and-views/#kb-your-server-does-not-have-xml-enabled" target="_blank">Importing Forms, Entries, and Views</a>'
40 );
41
42 return new WP_Error( 'SimpleXML_parse_error', $error_message, libxml_get_errors() );
43 }
44
45 $xml_string = file_get_contents( $file );
46 self::maybe_fix_xml( $xml_string );
47
48 $dom = new DOMDocument();
49
50 // LIBXML_COMPACT activates small nodes allocation optimization.
51 // Use LIBXML_PARSEHUGE to avoid "parser error : internal error: Huge input lookup" for large (300MB) files.
52 $success = $dom->loadXML( $xml_string, LIBXML_COMPACT | LIBXML_PARSEHUGE );
53 if ( ! $success ) {
54 return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this XML file', 'formidable' ), libxml_get_errors() );
55 }
56
57 if ( ! function_exists( 'simplexml_import_dom' ) ) {
58 return new WP_Error( 'SimpleXML_parse_error', __( 'Your server is missing the simplexml_import_dom function', 'formidable' ), libxml_get_errors() );
59 }
60
61 $xml = simplexml_import_dom( $dom );
62 unset( $dom );
63
64 // halt if loading produces an error
65 if ( ! $xml ) {
66 return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this XML file', 'formidable' ), libxml_get_errors() );
67 }
68
69 return self::import_xml_now( $xml );
70 }
71
72 /**
73 * @since 6.2.3
74 *
75 * @param string $xml_string
76 * @return void
77 */
78 private static function maybe_fix_xml( &$xml_string ) {
79 if ( '<?xml' !== substr( $xml_string, 0, 5 ) ) {
80 // Some XML files have may have unexpected characters at the start.
81 $xml_string = substr( $xml_string, strpos( $xml_string, '<?xml' ) );
82 }
83
84 // The Equity theme adds a <meta name="generator" content="Equity 1.7.13" /> tag using the "the_generator" filter.
85 // Strip that out as it breaks the XML import.
86 $channel_start_position = strpos( $xml_string, '<channel>' );
87 $content_before_channel_tag = substr( $xml_string, 0, $channel_start_position );
88 if ( 0 !== strpos( $content_before_channel_tag, '<meta name="generator" ' ) ) {
89 $content_before_channel_tag = preg_replace(
90 '/<meta\s+[^>]*name="generator"[^>]*\/>/i',
91 '',
92 $content_before_channel_tag,
93 1
94 );
95 $xml_string = $content_before_channel_tag . substr( $xml_string, $channel_start_position );
96 }
97 }
98
99 /**
100 * Add terms, forms (form and field ids), posts (post ids), and entries to db, in that order
101 *
102 * @since 3.06
103 *
104 * @param object $xml
105 * @param bool $installing_template
106 * @return array The number of items imported
107 */
108 public static function import_xml_now( $xml, $installing_template = false ) {
109 if ( ! defined( 'WP_IMPORTING' ) ) {
110 define( 'WP_IMPORTING', true );
111 }
112
113 self::$installing_template = $installing_template;
114 $imported = self::pre_import_data();
115
116 foreach ( array( 'term', 'form', 'view' ) as $item_type ) {
117 // Grab cats, tags, and terms, or forms or posts.
118 if ( isset( $xml->{$item_type} ) ) {
119 $function_name = 'import_xml_' . $item_type . 's';
120 $imported = self::$function_name( $xml->{$item_type}, $imported );
121 unset( $function_name, $xml->{$item_type} );
122 }
123 }
124
125 $imported = apply_filters( 'frm_importing_xml', $imported, $xml );
126
127 if ( ! isset( $imported['form_status'] ) || empty( $imported['form_status'] ) ) {
128 // Check for an error message in the XML.
129 if ( isset( $xml->Code ) && isset( $xml->Message ) ) { // phpcs:ignore WordPress.NamingConventions
130 $imported['error'] = (string) $xml->Message; // phpcs:ignore WordPress.NamingConventions
131 }
132 }
133
134 return $imported;
135 }
136
137 /**
138 * @since 3.06
139 * @return array
140 */
141 private static function pre_import_data() {
142 $defaults = array(
143 'forms' => 0,
144 'fields' => 0,
145 'terms' => 0,
146 'posts' => 0,
147 'views' => 0,
148 'actions' => 0,
149 'styles' => 0,
150 );
151
152 return array(
153 'imported' => $defaults,
154 'updated' => $defaults,
155 'forms' => array(),
156 'terms' => array(),
157 );
158 }
159
160 public static function import_xml_terms( $terms, $imported ) {
161 foreach ( $terms as $t ) {
162 if ( term_exists( (string) $t->term_slug, (string) $t->term_taxonomy ) ) {
163 continue;
164 }
165
166 $parent = self::get_term_parent_id( $t );
167
168 $term = wp_insert_term(
169 (string) $t->term_name,
170 (string) $t->term_taxonomy,
171 array(
172 'slug' => (string) $t->term_slug,
173 'description' => (string) $t->term_description,
174 'parent' => empty( $parent ) ? 0 : $parent,
175 )
176 );
177
178 if ( $term && is_array( $term ) ) {
179 $imported['imported']['terms'] ++;
180 $imported['terms'][ (int) $t->term_id ] = $term['term_id'];
181 }
182
183 unset( $term, $t );
184 }
185
186 return $imported;
187 }
188
189 /**
190 * @since 2.0.8
191 */
192 private static function get_term_parent_id( $t ) {
193 $parent = (string) $t->term_parent;
194 if ( ! empty( $parent ) ) {
195 $parent = term_exists( (string) $t->term_parent, (string) $t->term_taxonomy );
196 if ( $parent ) {
197 $parent = $parent['term_id'];
198 } else {
199 $parent = 0;
200 }
201 }
202
203 return $parent;
204 }
205
206 public static function import_xml_forms( $forms, $imported ) {
207 $child_forms = array();
208
209 // Import child forms first
210 self::put_child_forms_first( $forms );
211
212 foreach ( $forms as $item ) {
213 $form = self::fill_form( $item );
214
215 self::update_custom_style_setting_on_import( $form );
216
217 $this_form = self::maybe_get_form( $form );
218
219 $old_id = false;
220 $form_fields = false;
221 if ( ! empty( $this_form ) ) {
222 $form_id = $this_form->id;
223 $old_id = $this_form->id;
224 self::update_form( $this_form, $form, $imported );
225
226 $form_fields = self::get_form_fields( $form_id );
227 } else {
228 $form_id = FrmForm::create( $form );
229 if ( $form_id ) {
230 if ( empty( $form['parent_form_id'] ) ) {
231 // Don't include the repeater form in the imported count.
232 $imported['imported']['forms'] ++;
233 }
234
235 // Keep track of whether this specific form was updated or not.
236 $imported['form_status'][ $form_id ] = 'imported';
237 }
238 }
239
240 if ( $form_id ) {
241 self::track_imported_child_forms( (int) $form_id, $form['parent_form_id'], $child_forms );
242 }
243
244 self::import_xml_fields( $item->field, $form_id, $this_form, $form_fields, $imported );
245
246 self::delete_removed_fields( $form_fields );
247
248 // Update field ids/keys to new ones.
249 do_action( 'frm_after_duplicate_form', $form_id, $form, array( 'old_id' => $old_id ) );
250
251 $imported['forms'][ (int) $item->id ] = $form_id;
252
253 // Send pre 2.0 form options through function that creates actions.
254 self::migrate_form_settings_to_actions( $form['options'], $form_id, $imported, true );
255
256 do_action( 'frm_after_import_form', $form_id, $form );
257
258 unset( $form, $item );
259 }
260
261 self::maybe_update_child_form_parent_id( $imported['forms'], $child_forms );
262
263 return $imported;
264 }
265
266 private static function fill_form( $item ) {
267 $form = array(
268 'id' => (int) $item->id,
269 'form_key' => (string) $item->form_key,
270 'name' => (string) $item->name,
271 'description' => (string) $item->description,
272 'options' => (string) $item->options,
273 'logged_in' => (int) $item->logged_in,
274 'is_template' => (int) $item->is_template,
275 'editable' => (int) $item->editable,
276 'status' => (string) $item->status,
277 'parent_form_id' => isset( $item->parent_form_id ) ? (int) $item->parent_form_id : 0,
278 'created_at' => gmdate( 'Y-m-d H:i:s', strtotime( (string) $item->created_at ) ),
279 );
280
281 if ( empty( $item->created_at ) ) {
282 $form['created_at'] = current_time( 'mysql', 1 );
283 }
284
285 $form['options'] = FrmAppHelper::maybe_json_decode( $form['options'] );
286
287 if ( self::$installing_template ) {
288 // Templates don't necessarily have antispam on, but we want our templates to all have antispam on by default.
289 $form['options']['antispam'] = 1;
290 }
291
292 return $form;
293 }
294
295 private static function maybe_get_form( $form ) {
296 // if template, allow to edit if form keys match, otherwise, creation date must also match
297 $edit_query = array(
298 'form_key' => $form['form_key'],
299 'is_template' => $form['is_template'],
300 );
301 if ( ! $form['is_template'] ) {
302 $edit_query['created_at'] = $form['created_at'];
303 }
304
305 $edit_query = apply_filters( 'frm_match_xml_form', $edit_query, $form );
306
307 $form = FrmForm::getAll( $edit_query, '', 1 );
308 if ( is_object( $form ) && $form->status === 'trash' ) {
309 FrmForm::destroy( $form->id );
310 return false;
311 }
312
313 return $form;
314 }
315
316 private static function update_form( $this_form, $form, &$imported ) {
317 $form_id = $this_form->id;
318 FrmForm::update( $form_id, $form );
319 if ( empty( $form['parent_form_id'] ) ) {
320 // Don't include the repeater form in the updated count.
321 $imported['updated']['forms'] ++;
322 }
323
324 // Keep track of whether this specific form was updated or not
325 $imported['form_status'][ $form_id ] = 'updated';
326 }
327
328 private static function get_form_fields( $form_id ) {
329 $form_fields = FrmField::get_all_for_form( $form_id, '', 'exclude', 'exclude' );
330 $old_fields = array();
331 foreach ( $form_fields as $f ) {
332 $old_fields[ $f->id ] = $f;
333 $old_fields[ $f->field_key ] = $f->id;
334 unset( $f );
335 }
336 $form_fields = $old_fields;
337
338 return $form_fields;
339 }
340
341 /**
342 * Delete any fields attached to this form that were not included in the template
343 */
344 private static function delete_removed_fields( $form_fields ) {
345 if ( ! empty( $form_fields ) ) {
346 foreach ( $form_fields as $field ) {
347 if ( is_object( $field ) ) {
348 FrmField::destroy( $field->id );
349 }
350 unset( $field );
351 }
352 }
353 }
354
355 /**
356 * Put child forms first so they will be imported before parents
357 *
358 * @since 2.0.16
359 *
360 * @param array $forms
361 */
362 private static function put_child_forms_first( &$forms ) {
363 $child_forms = array();
364 $regular_forms = array();
365
366 foreach ( $forms as $form ) {
367 $parent_form_id = isset( $form->parent_form_id ) ? (int) $form->parent_form_id : 0;
368
369 if ( $parent_form_id ) {
370 $child_forms[] = $form;
371 } else {
372 $regular_forms[] = $form;
373 }
374 }
375
376 $forms = array_merge( $child_forms, $regular_forms );
377 }
378
379 /**
380 * Keep track of all imported child forms
381 *
382 * @since 2.0.16
383 *
384 * @param int $form_id
385 * @param int $parent_form_id
386 * @param array $child_forms
387 */
388 private static function track_imported_child_forms( $form_id, $parent_form_id, &$child_forms ) {
389 if ( $parent_form_id ) {
390 $child_forms[ $form_id ] = $parent_form_id;
391 }
392 }
393
394 /**
395 * Update the parent_form_id on imported child forms
396 * Child forms are imported first so their parent_form_id will need to be updated after the parent is imported
397 *
398 * @since 2.0.6
399 *
400 * @param array $imported_forms
401 * @param array $child_forms
402 */
403 private static function maybe_update_child_form_parent_id( $imported_forms, $child_forms ) {
404 foreach ( $child_forms as $child_form_id => $old_parent_form_id ) {
405 if ( isset( $imported_forms[ $old_parent_form_id ] ) && (int) $imported_forms[ $old_parent_form_id ] !== (int) $old_parent_form_id ) {
406 // Update all children with this old parent_form_id
407 $new_parent_form_id = (int) $imported_forms[ $old_parent_form_id ];
408 FrmForm::update( $child_form_id, array( 'parent_form_id' => $new_parent_form_id ) );
409 do_action( 'frm_update_child_form_parent_id', $child_form_id, $new_parent_form_id );
410 }
411 }
412 }
413
414 /**
415 * Import all fields for a form
416 *
417 * @since 2.0.13
418 *
419 * TODO: Cut down on params
420 */
421 private static function import_xml_fields( $xml_fields, $form_id, $this_form, &$form_fields, &$imported ) {
422 $in_section = 0;
423 $keys_by_original_field_id = array();
424
425 foreach ( $xml_fields as $field ) {
426 $f = self::fill_field( $field, $form_id );
427
428 self::set_default_value( $f );
429 self::maybe_add_required( $f );
430 self::maybe_update_in_section_variable( $in_section, $f );
431 self::maybe_update_form_select( $f, $imported );
432 self::maybe_update_get_values_form_setting( $imported, $f );
433 self::migrate_placeholders( $f );
434
435 if ( ! empty( $this_form ) ) {
436 // check for field to edit by field id
437 if ( isset( $form_fields[ $f['id'] ] ) ) {
438 FrmField::update( $f['id'], $f );
439 $imported['updated']['fields'] ++;
440
441 unset( $form_fields[ $f['id'] ] );
442
443 //unset old field key
444 if ( isset( $form_fields[ $f['field_key'] ] ) ) {
445 unset( $form_fields[ $f['field_key'] ] );
446 }
447 } elseif ( isset( $form_fields[ $f['field_key'] ] ) ) {
448 $keys_by_original_field_id[ $f['id'] ] = $f['field_key'];
449
450 // check for field to edit by field key
451 unset( $f['id'] );
452
453 FrmField::update( $form_fields[ $f['field_key'] ], $f );
454 $imported['updated']['fields'] ++;
455
456 unset( $form_fields[ $form_fields[ $f['field_key'] ] ] ); //unset old field id
457 unset( $form_fields[ $f['field_key'] ] ); //unset old field key
458 } else {
459 // if no matching field id or key in this form, create the field
460 self::create_imported_field( $f, $imported );
461 }
462 } else {
463
464 self::create_imported_field( $f, $imported );
465 }
466 }
467
468 if ( $keys_by_original_field_id ) {
469 self::maybe_update_field_ids( $form_id, $keys_by_original_field_id );
470 }
471 }
472
473 private static function fill_field( $field, $form_id ) {
474 return array(
475 'id' => (int) $field->id,
476 'field_key' => (string) $field->field_key,
477 'name' => (string) $field->name,
478 'description' => (string) $field->description,
479 'type' => (string) $field->type,
480 'default_value' => FrmAppHelper::maybe_json_decode( (string) $field->default_value ),
481 'field_order' => (int) $field->field_order,
482 'form_id' => (int) $form_id,
483 'required' => (int) $field->required,
484 'options' => FrmAppHelper::maybe_json_decode( (string) $field->options ),
485 'field_options' => FrmAppHelper::maybe_json_decode( (string) $field->field_options ),
486 );
487 }
488
489 /**
490 * @since 4.06
491 */
492 private static function set_default_value( &$f ) {
493 $has_default = array(
494 'text',
495 'email',
496 'url',
497 'textarea',
498 'number',
499 'phone',
500 'date',
501 'hidden',
502 'password',
503 'tag',
504 );
505
506 if ( is_array( $f['default_value'] ) && in_array( $f['type'], $has_default, true ) ) {
507 if ( count( $f['default_value'] ) === 1 ) {
508 $f['default_value'] = '[' . reset( $f['default_value'] ) . ']';
509 } else {
510 $f['default_value'] = reset( $f['default_value'] );
511 }
512 }
513 }
514
515 /**
516 * Make sure the required indicator is set.
517 *
518 * @since 4.05
519 */
520 private static function maybe_add_required( &$f ) {
521 if ( $f['required'] && ! isset( $f['field_options']['required_indicator'] ) ) {
522 $f['field_options']['required_indicator'] = '*';
523 }
524 }
525
526 /**
527 * Update the current in_section value at the beginning of the field loop
528 *
529 * @since 2.0.25
530 * @param int $in_section
531 * @param array $f
532 */
533 private static function maybe_update_in_section_variable( &$in_section, &$f ) {
534 // If we're at the end of a section, switch $in_section is 0
535 if ( in_array( $f['type'], array( 'end_divider', 'break', 'form' ) ) ) {
536 $in_section = 0;
537 }
538
539 // Update the current field's in_section value
540 if ( ! isset( $f['field_options']['in_section'] ) ) {
541 $f['field_options']['in_section'] = $in_section;
542 }
543
544 // If we're starting a new section, switch $in_section to ID of divider
545 if ( $f['type'] == 'divider' ) {
546 $in_section = $f['id'];
547 }
548 }
549
550 /**
551 * Switch the form_select on a repeating field or embedded form if it needs to be switched
552 *
553 * @since 2.0.16
554 *
555 * @param array $f
556 * @param array $imported
557 */
558 private static function maybe_update_form_select( &$f, $imported ) {
559 if ( ! isset( $imported['forms'] ) ) {
560 return;
561 }
562
563 if ( $f['type'] == 'form' || ( $f['type'] == 'divider' && FrmField::is_option_true( $f['field_options'], 'repeat' ) ) ) {
564 if ( FrmField::is_option_true( $f['field_options'], 'form_select' ) ) {
565 $form_select = (int) $f['field_options']['form_select'];
566 if ( isset( $imported['forms'][ $form_select ] ) ) {
567 $f['field_options']['form_select'] = $imported['forms'][ $form_select ];
568 }
569 }
570 }
571 }
572
573 /**
574 * Update the get_values_form setting if the form was imported
575 *
576 * @since 2.01.0
577 *
578 * @param array $imported
579 * @param array $f
580 */
581 private static function maybe_update_get_values_form_setting( $imported, &$f ) {
582 if ( ! isset( $imported['forms'] ) ) {
583 return;
584 }
585
586 if ( FrmField::is_option_true_in_array( $f['field_options'], 'get_values_form' ) ) {
587 $old_form = $f['field_options']['get_values_form'];
588 if ( isset( $imported['forms'][ $old_form ] ) ) {
589 $f['field_options']['get_values_form'] = $imported['forms'][ $old_form ];
590 }
591 }
592 }
593
594 /**
595 * If field settings have been migrated, update the values during import.
596 *
597 * @since 4.0
598 */
599 private static function run_field_migrations( &$f ) {
600 self::migrate_placeholders( $f );
601 $f = apply_filters( 'frm_import_xml_field', $f );
602 }
603
604 /**
605 * @since 4.0
606 */
607 private static function migrate_placeholders( &$f ) {
608 $update_values = self::migrate_field_placeholder( $f, 'clear_on_focus' );
609 foreach ( $update_values as $k => $v ) {
610 $f[ $k ] = $v;
611 }
612
613 $update_values = self::migrate_field_placeholder( $f, 'default_blank' );
614 foreach ( $update_values as $k => $v ) {
615 $f[ $k ] = $v;
616 }
617 }
618
619 /**
620 * Move clear_on_focus or default_blank to placeholder.
621 * Also called during database migration in FrmMigrate.
622 *
623 * @since 4.0
624 * @return array
625 */
626 public static function migrate_field_placeholder( $field, $type ) {
627 $field = (array) $field;
628 $field_options = $field['field_options'];
629 if ( empty( $field_options[ $type ] ) || empty( $field['default_value'] ) ) {
630 return array();
631 }
632
633 $field_options['placeholder'] = is_array( $field['default_value'] ) ? reset( $field['default_value'] ) : $field['default_value'];
634 unset( $field_options['default_blank'], $field_options['clear_on_focus'] );
635
636 $changes = array(
637 'field_options' => $field_options,
638 'default_value' => '',
639 );
640
641 // If a dropdown placeholder was used, remove the option so it won't be included twice.
642 $options = $field['options'];
643 if ( $type === 'default_blank' && is_array( $options ) ) {
644 $default_value = $field['default_value'];
645 if ( is_array( $default_value ) ) {
646 $default_value = reset( $default_value );
647 }
648
649 foreach ( $options as $opt_key => $opt ) {
650 if ( is_array( $opt ) ) {
651 $opt = isset( $opt['value'] ) ? $opt['value'] : ( isset( $opt['label'] ) ? $opt['label'] : reset( $opt ) );
652 }
653
654 if ( $opt == $default_value ) {
655 unset( $options[ $opt_key ] );
656 break;
657 }
658 }
659 $changes['options'] = $options;
660 }
661
662 return $changes;
663 }
664
665 /**
666 * Create an imported field
667 *
668 * @since 2.0.25
669 *
670 * @param array $f
671 * @param array $imported
672 */
673 private static function create_imported_field( $f, &$imported ) {
674 $defaults = self::default_field_options( $f['type'] );
675 $f['field_options'] = array_merge( $defaults, $f['field_options'] );
676
677 if ( is_callable( 'FrmProFileImport::import_attachment' ) ) {
678 $f = self::maybe_import_images_for_options( $f );
679 }
680
681 $new_id = FrmField::create( $f );
682 if ( $new_id != false ) {
683 $imported['imported']['fields'] ++;
684 do_action( 'frm_after_field_is_imported', $f, $new_id );
685 }
686 }
687
688 /**
689 * Import images for radio buttons and checkboxes from image src if available.
690 *
691 * @since 5.5.1
692 *
693 * @param array $field
694 * @return array
695 */
696 private static function maybe_import_images_for_options( $field ) {
697 if ( empty( $field['options'] ) || ! is_array( $field['options'] ) ) {
698 return $field;
699 }
700
701 foreach ( $field['options'] as $key => $option ) {
702 if ( ! is_array( $option ) || empty( $option['src'] ) ) {
703 continue;
704 }
705
706 $field_object = (object) $field;
707 $field_object->type = 'file'; // Fake the file type as FrmProImport::import_attachment checks for file type.
708
709 $image_id = FrmProFileImport::import_attachment( $option['src'], $field_object );
710 unset( $field['options'][ $key ]['src'] ); // Remove the src from options as it isn't required after import.
711
712 if ( is_numeric( $image_id ) ) {
713 $field['options'][ $key ]['image'] = $image_id;
714 }
715 }
716
717 return $field;
718 }
719
720 /**
721 * Fix field ids for fields that already exist prior to import.
722 *
723 * @since 4.07
724 * @param int $form_id
725 * @param array $keys_by_original_field_id
726 */
727 protected static function maybe_update_field_ids( $form_id, $keys_by_original_field_id ) {
728 global $frm_duplicate_ids;
729
730 $former_duplicate_ids = $frm_duplicate_ids;
731 $where = array(
732 array(
733 'or' => 1,
734 'fi.form_id' => $form_id,
735 'fr.parent_form_id' => $form_id,
736 ),
737 );
738 $fields = FrmField::getAll( $where, 'field_order' );
739 $field_id_by_key = wp_list_pluck( $fields, 'id', 'field_key' );
740
741 foreach ( $fields as $field ) {
742 $before = (array) clone $field;
743 $field = (array) $field;
744 $frm_duplicate_ids = $keys_by_original_field_id;
745 $after = FrmFieldsHelper::switch_field_ids( $field );
746
747 if ( $before['field_options'] !== $after['field_options'] ) {
748 $frm_duplicate_ids = $field_id_by_key;
749 $after = FrmFieldsHelper::switch_field_ids( $after );
750
751 if ( $before['field_options'] !== $after['field_options'] ) {
752 FrmField::update( $field['id'], array( 'field_options' => $after['field_options'] ) );
753 }
754 }
755 }
756
757 $frm_duplicate_ids = $former_duplicate_ids;
758 }
759
760 /**
761 * Updates the custom style setting on import
762 * Convert the post slug to an ID
763 *
764 * @since 2.0.19
765 *
766 * @param array $form
767 */
768 private static function update_custom_style_setting_on_import( &$form ) {
769 if ( ! isset( $form['options']['custom_style'] ) ) {
770 return;
771 }
772
773 if ( is_numeric( $form['options']['custom_style'] ) && 1 === intval( $form['options']['custom_style'] ) ) {
774 // Set to default
775 $form['options']['custom_style'] = 1;
776 } else {
777 // Replace the style name with the style ID on import
778 global $wpdb;
779 $table = $wpdb->prefix . 'posts';
780 $where = array(
781 'post_name' => $form['options']['custom_style'],
782 'post_type' => 'frm_styles',
783 );
784 $select = 'ID';
785 $style_id = FrmDb::get_var( $table, $where, $select );
786
787 if ( $style_id ) {
788 $form['options']['custom_style'] = $style_id;
789 } else {
790 // save the old style to maybe update after styles import
791 $form['options']['old_style'] = $form['options']['custom_style'];
792
793 // Set to default
794 $form['options']['custom_style'] = 1;
795 }
796 }
797 }
798
799 /**
800 * After styles are imported, check for any forms that were linked
801 * and link them back up.
802 *
803 * @since 2.2.7
804 */
805 private static function update_custom_style_setting_after_import( $form_id ) {
806 $form = FrmForm::getOne( $form_id );
807
808 if ( $form && isset( $form->options['old_style'] ) ) {
809 $form = (array) $form;
810 $saved_style = $form['options']['custom_style'];
811 $form['options']['custom_style'] = $form['options']['old_style'];
812 self::update_custom_style_setting_on_import( $form );
813 $has_changed = ( $form['options']['custom_style'] != $saved_style && $form['options']['custom_style'] != $form['options']['old_style'] );
814 if ( $has_changed ) {
815 FrmForm::update( $form['id'], $form );
816 }
817 }
818 }
819
820 public static function import_xml_views( $views, $imported ) {
821 $imported['posts'] = array();
822 $form_action_type = FrmFormActionsController::$action_post_type;
823
824 $post_types = array(
825 'frm_display' => 'views',
826 $form_action_type => 'actions',
827 'frm_styles' => 'styles',
828 );
829
830 $view_ids = array();
831 $posts_with_shortcodes = array();
832
833 foreach ( $views as $item ) {
834 $post = array(
835 'post_title' => (string) $item->title,
836 'post_name' => (string) $item->post_name,
837 'post_type' => (string) $item->post_type,
838 'post_password' => (string) $item->post_password,
839 'guid' => (string) $item->guid,
840 'post_status' => (string) $item->status,
841 'post_author' => FrmAppHelper::get_user_id_param( (string) $item->post_author ),
842 'post_id' => (int) $item->post_id,
843 'post_parent' => (int) $item->post_parent,
844 'menu_order' => (int) $item->menu_order,
845 'post_content' => FrmFieldsHelper::switch_field_ids( (string) $item->content ),
846 'post_excerpt' => FrmFieldsHelper::switch_field_ids( (string) $item->excerpt ),
847 'is_sticky' => (string) $item->is_sticky,
848 'comment_status' => (string) $item->comment_status,
849 'post_date' => (string) $item->post_date,
850 'post_date_gmt' => (string) $item->post_date_gmt,
851 'ping_status' => (string) $item->ping_status,
852 'postmeta' => array(),
853 'layout' => array(),
854 'tax_input' => array(),
855 );
856
857 $post['post_content'] = self::switch_form_ids( $post['post_content'], $imported['forms'] );
858
859 $old_id = $post['post_id'];
860 self::populate_post( $post, $item, $imported );
861
862 unset( $item );
863
864 $post_id = false;
865 if ( $post['post_type'] === $form_action_type ) {
866 $action_control = FrmFormActionsController::get_form_actions( $post['post_excerpt'] );
867 if ( $action_control && is_object( $action_control ) ) {
868 $post_id = $action_control->maybe_create_action( $post, $imported['form_status'] );
869 }
870 unset( $action_control );
871 } elseif ( $post['post_type'] === 'frm_styles' ) {
872 // Properly encode post content before inserting the post
873 $post['post_content'] = FrmAppHelper::maybe_json_decode( $post['post_content'] );
874 $post['post_content'] = FrmAppHelper::prepare_and_encode( $post['post_content'] );
875
876 // Create/update post now
877 $post_id = wp_insert_post( $post );
878 } else {
879 if ( $post['post_type'] === 'frm_display' ) {
880 $post['post_content'] = self::maybe_prepare_json_view_content( $post['post_content'] );
881 } elseif ( 'page' === $post['post_type'] && isset( $imported['posts'][ $post['post_parent'] ] ) ) {
882 $post['post_parent'] = $imported['posts'][ $post['post_parent'] ];
883 }
884 // Create/update post now
885 $post_id = wp_insert_post( $post );
886 }
887
888 if ( ! is_numeric( $post_id ) ) {
889 continue;
890 }
891
892 if ( false !== strpos( $post['post_content'], '[display-frm-data' ) || false !== strpos( $post['post_content'], '[formidable' ) ) {
893 $posts_with_shortcodes[ $post_id ] = $post;
894 }
895
896 self::update_postmeta( $post, $post_id );
897 self::update_layout( $post, $post_id );
898
899 $this_type = 'posts';
900 if ( isset( $post_types[ $post['post_type'] ] ) ) {
901 $this_type = $post_types[ $post['post_type'] ];
902 }
903
904 if ( isset( $post['ID'] ) && $post_id == $post['ID'] ) {
905 $imported['updated'][ $this_type ] ++;
906 } else {
907 $imported['imported'][ $this_type ] ++;
908 }
909
910 $imported['posts'][ (int) $old_id ] = $post_id;
911
912 if ( $post['post_type'] === 'frm_display' ) {
913 $view_ids[ (int) $old_id ] = $post_id;
914 }
915
916 do_action( 'frm_after_import_view', $post_id, $post );
917
918 unset( $post );
919 }
920
921 if ( $posts_with_shortcodes && $view_ids ) {
922 self::maybe_switch_view_ids_after_importing_posts( $posts_with_shortcodes, $view_ids );
923 }
924 unset( $posts_with_shortcodes, $view_ids );
925
926 if ( ! empty( $imported['forms'] ) ) {
927 // clear imported forms style cache to make sure the new styles are applied to the forms
928 self::clear_forms_style_caches( $imported['forms'] );
929 }
930
931 self::maybe_update_stylesheet( $imported );
932
933 flush_rewrite_rules();
934
935 return $imported;
936 }
937
938 /**
939 * Clears styles from cache for imported forms
940 *
941 * @param array $imported_forms
942 */
943 private static function clear_forms_style_caches( $imported_forms ) {
944 $where = array(
945 'id' => $imported_forms,
946 'options LIKE' => '"old_style"',
947 );
948 $forms = FrmDb::get_results( 'frm_forms', $where );
949
950 foreach ( $forms as $form ) {
951 FrmAppHelper::unserialize_or_decode( $form->options );
952 if ( ! $form->options ) {
953 continue;
954 }
955 $where = array(
956 'post_name' => $form->options['old_style'],
957 'post_type' => FrmStylesController::$post_type,
958 );
959
960 $select = 'ID';
961
962 $cache_key = FrmDb::generate_cache_key( $where, array( 'limit' => 1 ), $select, 'var' );
963 FrmDb::delete_cache_and_transient( $cache_key, 'post' );
964 }
965 }
966
967 /**
968 * Replace old form ids with new ones in a string.
969 *
970 * @param string $string
971 * @param array<int> $form_ids new form ids indexed by old form id.
972 * @return string
973 */
974 private static function switch_form_ids( $string, $form_ids ) {
975 if ( false === strpos( $string, '[formidable' ) ) {
976 // Skip string replacing if there are no form shortcodes in string.
977 return $string;
978 }
979
980 foreach ( $form_ids as $old_id => $new_id ) {
981 $string = str_replace(
982 array(
983 '[formidable id="' . $old_id . '"',
984 '[formidable id=' . $old_id . ']',
985 '[formidable id=' . $old_id . ' ',
986 '"formId":"' . $old_id . '"',
987 ),
988 array(
989 '[formidable id="' . $new_id . '"',
990 '[formidable id=' . $new_id . ']',
991 '[formidable id=' . $new_id . ' ',
992 '"formId":"' . $new_id . '"',
993 ),
994 $string
995 );
996 }
997
998 return $string;
999 }
1000
1001 /**
1002 * @param array<array> $posts_with_shortcodes indexed by current post id.
1003 * @param array<int> $view_ids new view ids indexed by old view id.
1004 * @return void
1005 */
1006 private static function maybe_switch_view_ids_after_importing_posts( $posts_with_shortcodes, $view_ids ) {
1007 foreach ( $posts_with_shortcodes as $imported_post_id => $post ) {
1008 $post_content = self::switch_view_ids( $post['post_content'], $view_ids );
1009 if ( $post_content === $post['post_content'] ) {
1010 continue;
1011 }
1012
1013 wp_update_post(
1014 array(
1015 'ID' => $imported_post_id,
1016 'post_content' => $post_content,
1017 )
1018 );
1019 }
1020 }
1021
1022 /**
1023 * Replace old view ids with new ones in a string.
1024 *
1025 * @param string $string
1026 * @param array<int> $view_ids new view ids indexed by old view id.
1027 * @return string
1028 */
1029 private static function switch_view_ids( $string, $view_ids ) {
1030 if ( false === strpos( $string, '[display-frm-data' ) ) {
1031 // Skip string replacing if there are no view shortcodes in string.
1032 return $string;
1033 }
1034
1035 foreach ( $view_ids as $old_id => $new_id ) {
1036 $string = str_replace(
1037 array(
1038 '[display-frm-data id="' . $old_id . '"',
1039 '[display-frm-data id=' . $old_id . ']',
1040 '[display-frm-data id=' . $old_id . ' ',
1041 '"viewId":"' . $old_id . '"',
1042 ),
1043 array(
1044 '[display-frm-data id="' . $new_id . '"',
1045 '[display-frm-data id=' . $new_id . ']',
1046 '[display-frm-data id=' . $new_id . ' ',
1047 '"viewId":"' . $new_id . '"',
1048 ),
1049 $string
1050 );
1051 unset( $old_id, $new_id );
1052 }
1053
1054 return $string;
1055 }
1056
1057 /**
1058 * @param string $content
1059 * @return string
1060 */
1061 private static function maybe_prepare_json_view_content( $content ) {
1062 $maybe_decoded = FrmAppHelper::maybe_json_decode( $content );
1063 if ( is_array( $maybe_decoded ) && isset( $maybe_decoded[0] ) && isset( $maybe_decoded[0]['box'] ) ) {
1064 return FrmAppHelper::prepare_and_encode( $maybe_decoded );
1065 }
1066 return $content;
1067 }
1068
1069 private static function populate_post( &$post, $item, $imported ) {
1070 if ( isset( $item->attachment_url ) ) {
1071 $post['attachment_url'] = (string) $item->attachment_url;
1072 }
1073
1074 if ( $post['post_type'] == FrmFormActionsController::$action_post_type && isset( $imported['forms'][ (int) $post['menu_order'] ] ) ) {
1075 // update to new form id
1076 $post['menu_order'] = $imported['forms'][ (int) $post['menu_order'] ];
1077 }
1078
1079 // Don't allow default styles to take over a site's default style
1080 if ( 'frm_styles' == $post['post_type'] ) {
1081 $post['menu_order'] = 0;
1082 }
1083
1084 foreach ( $item->postmeta as $meta ) {
1085 self::populate_postmeta( $post, $meta, $imported );
1086 unset( $meta );
1087 }
1088
1089 foreach ( $item->layout as $layout ) {
1090 self::populate_layout( $post, $layout );
1091 unset( $layout );
1092 }
1093
1094 self::populate_taxonomies( $post, $item );
1095
1096 self::maybe_editing_post( $post );
1097 }
1098
1099 /**
1100 * @param array $post
1101 * @param stdClass $meta
1102 * @param array $imported
1103 */
1104 private static function populate_postmeta( &$post, $meta, $imported ) {
1105 global $frm_duplicate_ids;
1106
1107 $m = array(
1108 'key' => (string) $meta->meta_key,
1109 'value' => (string) $meta->meta_value,
1110 );
1111
1112 //switch old form and field ids to new ones
1113 if ( 'frm_form_id' === $m['key'] && isset( $imported['forms'][ (int) $m['value'] ] ) ) {
1114 $m['value'] = $imported['forms'][ (int) $m['value'] ];
1115 } else {
1116 $m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
1117
1118 if ( ! empty( $frm_duplicate_ids ) ) {
1119 if ( 'frm_dyncontent' === $m['key'] ) {
1120 $m['value'] = self::maybe_prepare_json_view_content( $m['value'] );
1121 $m['value'] = FrmFieldsHelper::switch_field_ids( $m['value'] );
1122 } elseif ( 'frm_options' === $m['key'] ) {
1123
1124 foreach ( array( 'date_field_id', 'edate_field_id' ) as $setting_name ) {
1125 if ( isset( $m['value'][ $setting_name ] ) && is_numeric( $m['value'][ $setting_name ] ) && isset( $frm_duplicate_ids[ $m['value'][ $setting_name ] ] ) ) {
1126 $m['value'][ $setting_name ] = $frm_duplicate_ids[ $m['value'][ $setting_name ] ];
1127 }
1128 }
1129
1130 $check_dup_array = array();
1131 if ( isset( $m['value']['order_by'] ) && ! empty( $m['value']['order_by'] ) ) {
1132 if ( is_numeric( $m['value']['order_by'] ) && isset( $frm_duplicate_ids[ $m['value']['order_by'] ] ) ) {
1133 $m['value']['order_by'] = $frm_duplicate_ids[ $m['value']['order_by'] ];
1134 } elseif ( is_array( $m['value']['order_by'] ) ) {
1135 $check_dup_array[] = 'order_by';
1136 }
1137 }
1138
1139 if ( isset( $m['value']['where'] ) && ! empty( $m['value']['where'] ) ) {
1140 $check_dup_array[] = 'where';
1141 }
1142
1143 foreach ( $check_dup_array as $check_k ) {
1144 foreach ( (array) $m['value'][ $check_k ] as $mk => $mv ) {
1145 if ( isset( $frm_duplicate_ids[ $mv ] ) ) {
1146 $m['value'][ $check_k ][ $mk ] = $frm_duplicate_ids[ $mv ];
1147 }
1148 unset( $mk, $mv );
1149 }
1150 }
1151 }
1152 }
1153 }
1154
1155 if ( ! is_array( $m['value'] ) ) {
1156 $m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
1157 }
1158
1159 $post['postmeta'][ (string) $meta->meta_key ] = $m['value'];
1160 }
1161
1162 private static function populate_layout( &$post, $layout ) {
1163 $post['layout'][ (string) $layout->type ] = (string) $layout->data;
1164 }
1165
1166 /**
1167 * Add terms to post
1168 *
1169 * @param array $post by reference
1170 * @param object $item The XML object data
1171 */
1172 private static function populate_taxonomies( &$post, $item ) {
1173 foreach ( $item->category as $c ) {
1174 $att = $c->attributes();
1175 if ( ! isset( $att['nicename'] ) ) {
1176 continue;
1177 }
1178
1179 $taxonomy = (string) $att['domain'];
1180 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1181 $name = (string) $att['nicename'];
1182 $h_term = get_term_by( 'slug', $name, $taxonomy );
1183 if ( $h_term ) {
1184 $name = $h_term->term_id;
1185 }
1186 unset( $h_term );
1187 } else {
1188 $name = (string) $c;
1189 }
1190
1191 if ( ! isset( $post['tax_input'][ $taxonomy ] ) ) {
1192 $post['tax_input'][ $taxonomy ] = array();
1193 }
1194
1195 $post['tax_input'][ $taxonomy ][] = $name;
1196 unset( $name );
1197 }
1198 }
1199
1200 /**
1201 * Edit post if the key and created time match
1202 */
1203 private static function maybe_editing_post( &$post ) {
1204 $match_by = array(
1205 'post_type' => $post['post_type'],
1206 'name' => $post['post_name'],
1207 'post_status' => $post['post_status'],
1208 'posts_per_page' => 1,
1209 );
1210
1211 if ( in_array( $post['post_status'], array( 'trash', 'draft' ) ) ) {
1212 $match_by['include'] = $post['post_id'];
1213 unset( $match_by['name'] );
1214 }
1215
1216 $editing = get_posts( $match_by );
1217
1218 if ( ! empty( $editing ) && current( $editing )->post_date == $post['post_date'] ) {
1219 // set the id of the post to edit
1220 $post['ID'] = current( $editing )->ID;
1221 }
1222 }
1223
1224 /**
1225 * @param array $post
1226 * @param int $post_id
1227 * @return void
1228 */
1229 private static function update_postmeta( &$post, $post_id ) {
1230 foreach ( $post['postmeta'] as $k => $v ) {
1231 switch ( $k ) {
1232 case '_edit_last':
1233 $v = FrmAppHelper::get_user_id_param( $v );
1234 break;
1235
1236 case '_thumbnail_id':
1237 if ( FrmAppHelper::pro_is_installed() ) {
1238 // Change the attachment ID.
1239 $field_obj = FrmFieldFactory::get_field_type( 'file' );
1240 $v = $field_obj->get_file_id( $v );
1241 }
1242 break;
1243
1244 case 'frm_dyncontent':
1245 if ( is_array( $v ) ) {
1246 $v = json_encode( $v );
1247 }
1248 break;
1249
1250 case 'frm_param':
1251 add_rewrite_endpoint( $v, EP_PERMALINK | EP_PAGES );
1252 break;
1253 }
1254
1255 update_post_meta( $post_id, $k, $v );
1256 unset( $k, $v );
1257 }
1258 }
1259
1260 /**
1261 * @param array $post
1262 * @param int $post_id
1263 */
1264 private static function update_layout( &$post, $post_id ) {
1265 if ( is_callable( 'FrmViewsLayout::maybe_create_layouts_for_view' ) ) {
1266 $listing_layout = ! empty( $post['layout']['listing'] ) ? json_decode( $post['layout']['listing'], true ) : array();
1267 $detail_layout = ! empty( $post['layout']['detail'] ) ? json_decode( $post['layout']['detail'], true ) : array();
1268 if ( $listing_layout || $detail_layout ) {
1269 FrmViewsLayout::maybe_create_layouts_for_view( $post_id, $listing_layout, $detail_layout );
1270 }
1271 }
1272 }
1273
1274 private static function maybe_update_stylesheet( $imported ) {
1275 $new_styles = isset( $imported['imported']['styles'] ) && ! empty( $imported['imported']['styles'] );
1276 $updated_styles = isset( $imported['updated']['styles'] ) && ! empty( $imported['updated']['styles'] );
1277 if ( $new_styles || $updated_styles ) {
1278 if ( is_admin() && function_exists( 'get_filesystem_method' ) ) {
1279 $frm_style = new FrmStyle();
1280 $frm_style->update( 'default' );
1281 }
1282 foreach ( $imported['forms'] as $form_id ) {
1283 self::update_custom_style_setting_after_import( $form_id );
1284 }
1285 }
1286 }
1287
1288 /**
1289 * @param string $message
1290 */
1291 public static function parse_message( $result, &$message, &$errors ) {
1292 if ( is_wp_error( $result ) ) {
1293 $errors[] = $result->get_error_message();
1294
1295 // Remove the SimpleXML_parse_error from the WP_Error object to avoid
1296 // displaying duplicate error messages from $result->get_error_message()
1297 $error_codes = $result->get_error_codes();
1298 $error_details = array();
1299 foreach ( $error_codes as $error_code ) {
1300 // Clone WP_Error data because WP_Error removes all error messages and data
1301 // associated with the specified error code when an item is removed.
1302 // Source: https://developer.wordpress.org/reference/classes/wp_error/remove/#source
1303 $error_details = $result->get_error_data( $error_code );
1304 if ( $error_code === 'SimpleXML_parse_error' ) {
1305 $result->remove( $error_code );
1306 break;
1307 }
1308 }
1309
1310 if ( ! empty( $error_details ) ) {
1311 $errors[] = '<br />' . esc_html_x( 'Error details:', 'import xml message', 'formidable' ) . '<br />' . esc_html( print_r( $error_details, 1 ) );
1312 }
1313
1314 return;
1315 } elseif ( ! $result ) {
1316 return;
1317 }
1318
1319 if ( ! is_array( $result ) ) {
1320 $message = is_string( $result ) ? $result : htmlentities( print_r( $result, 1 ) );
1321
1322 return;
1323 }
1324
1325 $t_strings = array(
1326 'imported' => __( 'Imported', 'formidable' ),
1327 'updated' => __( 'Updated', 'formidable' ),
1328 );
1329
1330 $message = '<ul>';
1331 foreach ( $result as $type => $results ) {
1332 if ( ! isset( $t_strings[ $type ] ) ) {
1333 // only print imported and updated
1334 continue;
1335 }
1336
1337 $s_message = array();
1338 foreach ( $results as $k => $m ) {
1339 self::item_count_message( $m, $k, $s_message );
1340 unset( $k, $m );
1341 }
1342
1343 if ( ! empty( $s_message ) ) {
1344 $message .= '<li><strong>' . $t_strings[ $type ] . ':</strong> ';
1345 $message .= implode( ', ', $s_message );
1346 $message .= '</li>';
1347 }
1348 }
1349
1350 if ( $message == '<ul>' ) {
1351 $message = '';
1352 $errors[] = __( 'Nothing was imported or updated', 'formidable' );
1353 } else {
1354 self::add_form_link_to_message( $result, $message );
1355
1356 /**
1357 * @since 5.3
1358 *
1359 * @param string $message
1360 * @param array $result
1361 */
1362 $message = apply_filters( 'frm_xml_parsed_message', $message, $result );
1363 $message .= '</ul>';
1364 }
1365 }
1366
1367 /**
1368 * @param int $m
1369 * @param string $type
1370 * @param array<string> $s_message
1371 */
1372 public static function item_count_message( $m, $type, &$s_message ) {
1373 if ( ! $m ) {
1374 return;
1375 }
1376
1377 $strings = array(
1378 /* translators: %1$s: Number of items */
1379 'forms' => sprintf( _n( '%1$s Form', '%1$s Forms', $m, 'formidable' ), $m ),
1380 /* translators: %1$s: Number of items */
1381 'fields' => sprintf( _n( '%1$s Field', '%1$s Fields', $m, 'formidable' ), $m ),
1382 /* translators: %1$s: Number of items */
1383 'items' => sprintf( _n( '%1$s Entry', '%1$s Entries', $m, 'formidable' ), $m ),
1384 /* translators: %1$s: Number of items */
1385 'views' => sprintf( _n( '%1$s View', '%1$s Views', $m, 'formidable' ), $m ),
1386 /* translators: %1$s: Number of items */
1387 'posts' => sprintf( _n( '%1$s Page/Post', '%1$s Pages/Posts', $m, 'formidable' ), $m ),
1388 /* translators: %1$s: Number of items */
1389 'styles' => sprintf( _n( '%1$s Style', '%1$s Styles', $m, 'formidable' ), $m ),
1390 /* translators: %1$s: Number of items */
1391 'terms' => sprintf( _n( '%1$s Term', '%1$s Terms', $m, 'formidable' ), $m ),
1392 /* translators: %1$s: Number of items */
1393 'actions' => sprintf( _n( '%1$s Form Action', '%1$s Form Actions', $m, 'formidable' ), $m ),
1394 );
1395
1396 if ( isset( $strings[ $type ] ) ) {
1397 $s_message[] = $strings[ $type ];
1398 } else {
1399 $string = ' ' . $m . ' ' . ucfirst( $type );
1400
1401 /**
1402 * @since 5.3
1403 *
1404 * @param string $string Message string for imported item.
1405 * @param int $m Number of item that was imported.
1406 * }
1407 */
1408 $string = apply_filters( 'frm_xml_' . $type . '_count_message', $string, $m );
1409 $s_message[] = $string;
1410 }
1411 }
1412
1413 /**
1414 * If a single form was imported, include a link in the success message.
1415 *
1416 * @since 4.0
1417 * @param array $result The response from the XML import.
1418 * @param string $message The response shown on the page after import.
1419 */
1420 private static function add_form_link_to_message( $result, &$message ) {
1421 $total_forms = $result['imported']['forms'] + $result['updated']['forms'];
1422 if ( $total_forms > 1 ) {
1423 return;
1424 }
1425
1426 $primary_form = reset( $result['forms'] );
1427 if ( ! empty( $primary_form ) ) {
1428 $primary_form = FrmForm::getOne( $primary_form );
1429 $form_id = empty( $primary_form->parent_form_id ) ? $primary_form->id : $primary_form->parent_form_id;
1430
1431 $message .= '<li><a href="' . esc_url( FrmForm::get_edit_link( $form_id ) ) . '">' . esc_html__( 'Go to imported form', 'formidable' ) . '</a></li>';
1432 }
1433 }
1434
1435 /**
1436 * Prepare the form options for export
1437 *
1438 * @since 2.0.19
1439 *
1440 * @param string $options
1441 *
1442 * @return string
1443 */
1444 public static function prepare_form_options_for_export( $options ) {
1445 FrmAppHelper::unserialize_or_decode( $options );
1446 // Change custom_style to the post_name instead of ID (1 may be a string)
1447 $not_default = isset( $options['custom_style'] ) && 1 != $options['custom_style'];
1448 if ( $not_default ) {
1449 global $wpdb;
1450 $table = $wpdb->prefix . 'posts';
1451 $where = array( 'ID' => $options['custom_style'] );
1452 $select = 'post_name';
1453
1454 $style_name = FrmDb::get_var( $table, $where, $select );
1455
1456 if ( $style_name ) {
1457 $options['custom_style'] = $style_name;
1458 } else {
1459 $options['custom_style'] = 1;
1460 }
1461 }
1462 self::remove_default_form_options( $options );
1463 $options = serialize( $options );
1464
1465 return self::cdata( $options );
1466 }
1467
1468 /**
1469 * If the saved value is the same as the default, remove it from the export
1470 * This keeps file size down and prevents overriding global settings after import
1471 *
1472 * @since 3.06
1473 */
1474 private static function remove_default_form_options( &$options ) {
1475 $defaults = FrmFormsHelper::get_default_opts();
1476 if ( is_callable( 'FrmProFormsHelper::get_default_opts' ) ) {
1477 $defaults += FrmProFormsHelper::get_default_opts();
1478 }
1479 self::remove_defaults( $defaults, $options );
1480 }
1481
1482 /**
1483 * Remove extra settings from field to keep file size down
1484 *
1485 * @since 3.06
1486 */
1487 public static function prepare_field_for_export( &$field ) {
1488 self::remove_default_field_options( $field );
1489 self::add_image_src_to_image_options( $field );
1490 }
1491
1492 /**
1493 * Remove defaults from field options too
1494 *
1495 * @since 3.06
1496 */
1497 private static function remove_default_field_options( &$field ) {
1498 $defaults = self::default_field_options( $field->type );
1499 if ( empty( $defaults['blank'] ) ) {
1500 $global_settings = new FrmSettings();
1501 $global_defaults = $global_settings->default_options();
1502 $defaults['blank'] = $global_defaults['blank_msg'];
1503 }
1504
1505 $options = $field->field_options;
1506 FrmAppHelper::unserialize_or_decode( $options );
1507 self::remove_defaults( $defaults, $options );
1508 self::remove_default_html( 'custom_html', $defaults, $options );
1509
1510 // Get variations on the defaults.
1511 if ( isset( $options['invalid'] ) ) {
1512 $defaults = array(
1513 /* translators: %s: Field name */
1514 'invalid' => sprintf( __( '%s is invalid', 'formidable' ), $field->name ),
1515 );
1516 self::remove_defaults( $defaults, $options );
1517 }
1518
1519 $field->field_options = serialize( $options );
1520 }
1521
1522 /**
1523 * Add image "src" key to each image option so the image can be imported to another website.
1524 *
1525 * @since 5.5.1
1526 *
1527 * @param stdClass $field
1528 * @return void
1529 */
1530 private static function add_image_src_to_image_options( $field ) {
1531 if ( empty( $field->options ) || false === strpos( $field->options, 'image' ) ) {
1532 return;
1533 }
1534
1535 $updated = false;
1536 $options = $field->options;
1537 FrmAppHelper::unserialize_or_decode( $options );
1538
1539 if ( ! $options || ! is_array( $options ) ) {
1540 return;
1541 }
1542
1543 foreach ( $options as $key => $option ) {
1544 if ( is_array( $option ) && ! empty( $option['image'] ) ) {
1545 $options[ $key ]['src'] = wp_get_attachment_url( $option['image'] );
1546 $updated = true;
1547 }
1548 }
1549
1550 if ( $updated ) {
1551 $field->options = maybe_serialize( $options );
1552 }
1553 }
1554
1555 /**
1556 * @since 3.06.03
1557 */
1558 private static function default_field_options( $type ) {
1559 $defaults = FrmFieldsHelper::get_default_field_options( $type );
1560 if ( empty( $defaults['custom_html'] ) ) {
1561 $defaults['custom_html'] = FrmFieldsHelper::get_default_html( $type );
1562 }
1563 return $defaults;
1564 }
1565
1566 /**
1567 * Compare the default array to the saved values and
1568 * remove if they are the same
1569 *
1570 * @since 3.06
1571 */
1572 private static function remove_defaults( $defaults, &$saved ) {
1573 foreach ( $saved as $key => $value ) {
1574 if ( isset( $defaults[ $key ] ) && $defaults[ $key ] === $value ) {
1575 unset( $saved[ $key ] );
1576 }
1577 }
1578 }
1579
1580 /**
1581 * The line endings may prevent html from being equal when it should
1582 *
1583 * @since 3.06
1584 */
1585 private static function remove_default_html( $html_name, $defaults, &$options ) {
1586 if ( ! isset( $options[ $html_name ] ) || ! isset( $defaults[ $html_name ] ) ) {
1587 return;
1588 }
1589
1590 $old_html = str_replace( "\r\n", "\n", $options[ $html_name ] );
1591 $default_html = $defaults[ $html_name ];
1592 if ( $old_html == $default_html ) {
1593 unset( $options[ $html_name ] );
1594
1595 return;
1596 }
1597
1598 // Account for some of the older field default HTML.
1599 $default_html = str_replace( ' id="frm_desc_field_[key]"', '', $default_html );
1600 if ( $old_html == $default_html ) {
1601 unset( $options[ $html_name ] );
1602 }
1603 }
1604
1605 public static function cdata( $str ) {
1606 FrmAppHelper::unserialize_or_decode( $str );
1607 if ( is_array( $str ) ) {
1608 $str = json_encode( $str );
1609 } elseif ( seems_utf8( $str ) === false ) {
1610 $str = FrmAppHelper::maybe_utf8_encode( $str );
1611 }
1612
1613 if ( is_numeric( $str ) ) {
1614 return $str;
1615 }
1616
1617 self::remove_invalid_characters_from_xml( $str );
1618
1619 // $str = ent2ncr(esc_html( $str));
1620 $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
1621
1622 return $str;
1623 }
1624
1625 /**
1626 * Remove <US> character (unit separator) from exported strings
1627 *
1628 * @since 2.0.22
1629 *
1630 * @param string $str
1631 */
1632 private static function remove_invalid_characters_from_xml( &$str ) {
1633 // Remove <US> character
1634 $str = str_replace( '\x1F', '', $str );
1635 }
1636
1637 public static function migrate_form_settings_to_actions( $form_options, $form_id, &$imported = array(), $switch = false ) {
1638 // Get post type
1639 $post_type = FrmFormActionsController::$action_post_type;
1640
1641 // Set up imported index, if not set up yet
1642 if ( ! isset( $imported['imported']['actions'] ) ) {
1643 $imported['imported']['actions'] = 0;
1644 }
1645
1646 // Migrate post settings to action
1647 self::migrate_post_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1648
1649 // Migrate email settings to action
1650 self::migrate_email_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1651 }
1652
1653 /**
1654 * Migrate post settings to form action
1655 *
1656 * @param string $post_type
1657 */
1658 private static function migrate_post_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1659 if ( ! isset( $form_options['create_post'] ) || ! $form_options['create_post'] ) {
1660 return;
1661 }
1662
1663 $new_action = array(
1664 'post_type' => $post_type,
1665 'post_excerpt' => 'wppost',
1666 'post_title' => __( 'Create Posts', 'formidable' ),
1667 'menu_order' => $form_id,
1668 'post_status' => 'publish',
1669 'post_content' => array(),
1670 'post_name' => $form_id . '_wppost_1',
1671 );
1672
1673 $post_settings = array(
1674 'post_type',
1675 'post_category',
1676 'post_content',
1677 'post_excerpt',
1678 'post_title',
1679 'post_name',
1680 'post_date',
1681 'post_status',
1682 'post_custom_fields',
1683 'post_password',
1684 'post_parent',
1685 );
1686
1687 foreach ( $post_settings as $post_setting ) {
1688 if ( isset( $form_options[ $post_setting ] ) ) {
1689 $new_action['post_content'][ $post_setting ] = $form_options[ $post_setting ];
1690 }
1691 unset( $post_setting );
1692 }
1693
1694 $new_action['event'] = array( 'create', 'update' );
1695
1696 if ( $switch ) {
1697 // Fields with string or int saved.
1698 $basic_fields = array(
1699 'post_title',
1700 'post_content',
1701 'post_excerpt',
1702 'post_password',
1703 'post_date',
1704 'post_status',
1705 'post_parent',
1706 );
1707
1708 // Fields with arrays saved.
1709 $array_fields = array( 'post_category', 'post_custom_fields' );
1710
1711 $new_action['post_content'] = self::switch_action_field_ids( $new_action['post_content'], $basic_fields, $array_fields );
1712 }
1713 $new_action['post_content'] = json_encode( $new_action['post_content'] );
1714
1715 $exists = get_posts(
1716 array(
1717 'name' => $new_action['post_name'],
1718 'post_type' => $new_action['post_type'],
1719 'post_status' => $new_action['post_status'],
1720 'numberposts' => 1,
1721 )
1722 );
1723
1724 if ( ! $exists ) {
1725 // this isn't an email, but we need to use a class that will always be included
1726 FrmDb::save_json_post( $new_action );
1727 $imported['imported']['actions'] ++;
1728 }
1729 }
1730
1731 /**
1732 * Switch old field IDs for new field IDs in emails and post
1733 *
1734 * @since 2.0
1735 *
1736 * @param array $post_content - check for old field IDs
1737 * @param array $basic_fields - fields with string or int saved
1738 * @param array $array_fields - fields with arrays saved
1739 *
1740 * @return string $post_content - new field IDs
1741 */
1742 private static function switch_action_field_ids( $post_content, $basic_fields, $array_fields = array() ) {
1743 global $frm_duplicate_ids;
1744
1745 // If there aren't IDs that were switched, end now
1746 if ( ! $frm_duplicate_ids ) {
1747 return;
1748 }
1749
1750 // Get old IDs
1751 $old = array_keys( $frm_duplicate_ids );
1752
1753 // Get new IDs
1754 $new = array_values( $frm_duplicate_ids );
1755
1756 // Do a str_replace with each item to set the new IDs
1757 foreach ( $post_content as $key => $setting ) {
1758 if ( ! is_array( $setting ) && in_array( $key, $basic_fields ) ) {
1759 // Replace old IDs with new IDs
1760 $post_content[ $key ] = str_replace( $old, $new, $setting );
1761 } elseif ( is_array( $setting ) && in_array( $key, $array_fields ) ) {
1762 foreach ( $setting as $k => $val ) {
1763 // Replace old IDs with new IDs
1764 $post_content[ $key ][ $k ] = str_replace( $old, $new, $val );
1765 }
1766 }
1767 unset( $key, $setting );
1768 }
1769
1770 return $post_content;
1771 }
1772
1773 private static function migrate_email_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1774 // No old notifications or autoresponders to carry over
1775 if ( ! isset( $form_options['auto_responder'] ) && ! isset( $form_options['notification'] ) && ! isset( $form_options['email_to'] ) ) {
1776 return;
1777 }
1778
1779 // Initialize notifications array
1780 $notifications = array();
1781
1782 // Migrate regular notifications
1783 self::migrate_notifications_to_action( $form_options, $form_id, $notifications );
1784
1785 // Migrate autoresponders
1786 self::migrate_autoresponder_to_action( $form_options, $form_id, $notifications );
1787
1788 if ( empty( $notifications ) ) {
1789 return;
1790 }
1791
1792 foreach ( $notifications as $new_notification ) {
1793 $new_notification['post_type'] = $post_type;
1794 $new_notification['post_excerpt'] = 'email';
1795 $new_notification['post_title'] = __( 'Email Notification', 'formidable' );
1796 $new_notification['menu_order'] = $form_id;
1797 $new_notification['post_status'] = 'publish';
1798
1799 // Switch field IDs and keys, if needed
1800 if ( $switch ) {
1801
1802 // Switch field IDs in email conditional logic
1803 self::switch_email_condition_field_ids( $new_notification['post_content'] );
1804
1805 // Switch all other field IDs in email
1806 $new_notification['post_content'] = FrmFieldsHelper::switch_field_ids( $new_notification['post_content'] );
1807 }
1808 $new_notification['post_content'] = FrmAppHelper::prepare_and_encode( $new_notification['post_content'] );
1809
1810 $exists = get_posts(
1811 array(
1812 'name' => $new_notification['post_name'],
1813 'post_type' => $new_notification['post_type'],
1814 'post_status' => $new_notification['post_status'],
1815 'numberposts' => 1,
1816 )
1817 );
1818
1819 if ( empty( $exists ) ) {
1820 FrmDb::save_json_post( $new_notification );
1821 $imported['imported']['actions'] ++;
1822 }
1823 unset( $new_notification );
1824 }
1825
1826 self::remove_deprecated_notification_settings( $form_id, $form_options );
1827 }
1828
1829 /**
1830 * Remove deprecated notification settings after migration
1831 *
1832 * @since 2.05
1833 *
1834 * @param int|string $form_id
1835 * @param array $form_options
1836 */
1837 private static function remove_deprecated_notification_settings( $form_id, $form_options ) {
1838 $delete_settings = array( 'notification', 'autoresponder', 'email_to' );
1839 foreach ( $delete_settings as $index ) {
1840 if ( isset( $form_options[ $index ] ) ) {
1841 unset( $form_options[ $index ] );
1842 }
1843 }
1844 FrmForm::update( $form_id, array( 'options' => $form_options ) );
1845 }
1846
1847 private static function migrate_notifications_to_action( $form_options, $form_id, &$notifications ) {
1848 if ( ! isset( $form_options['notification'] ) && isset( $form_options['email_to'] ) && ! empty( $form_options['email_to'] ) ) {
1849 // add old settings into notification array
1850 $form_options['notification'] = array( 0 => $form_options );
1851 } elseif ( isset( $form_options['notification']['email_to'] ) ) {
1852 // make sure it's in the correct format
1853 $form_options['notification'] = array( 0 => $form_options['notification'] );
1854 }
1855
1856 if ( isset( $form_options['notification'] ) && is_array( $form_options['notification'] ) ) {
1857 foreach ( $form_options['notification'] as $email_key => $notification ) {
1858
1859 $atts = array(
1860 'email_to' => '',
1861 'reply_to' => '',
1862 'reply_to_name' => '',
1863 'event' => '',
1864 'form_id' => $form_id,
1865 'email_key' => $email_key,
1866 );
1867
1868 // Format the email data
1869 self::format_email_data( $atts, $notification );
1870
1871 if ( isset( $notification['twilio'] ) && $notification['twilio'] ) {
1872 do_action( 'frm_create_twilio_action', $atts, $notification );
1873 }
1874
1875 // Setup the new notification
1876 $new_notification = array();
1877 self::setup_new_notification( $new_notification, $notification, $atts );
1878
1879 $notifications[] = $new_notification;
1880 }
1881 }
1882 }
1883
1884 private static function format_email_data( &$atts, $notification ) {
1885 // Format email_to
1886 self::format_email_to_data( $atts, $notification );
1887
1888 // Format the reply to email and name
1889 $reply_fields = array(
1890 'reply_to' => '',
1891 'reply_to_name' => '',
1892 );
1893 foreach ( $reply_fields as $f => $val ) {
1894 if ( isset( $notification[ $f ] ) ) {
1895 $atts[ $f ] = $notification[ $f ];
1896 if ( 'custom' == $notification[ $f ] ) {
1897 $atts[ $f ] = $notification[ 'cust_' . $f ];
1898 } elseif ( is_numeric( $atts[ $f ] ) && ! empty( $atts[ $f ] ) ) {
1899 $atts[ $f ] = '[' . $atts[ $f ] . ']';
1900 }
1901 }
1902 unset( $f, $val );
1903 }
1904
1905 // Format event
1906 $atts['event'] = array( 'create' );
1907 if ( isset( $notification['update_email'] ) && 1 == $notification['update_email'] ) {
1908 $atts['event'][] = 'update';
1909 } elseif ( isset( $notification['update_email'] ) && 2 == $notification['update_email'] ) {
1910 $atts['event'] = array( 'update' );
1911 }
1912 }
1913
1914 private static function format_email_to_data( &$atts, $notification ) {
1915 if ( isset( $notification['email_to'] ) ) {
1916 $atts['email_to'] = preg_split( '/ (,|;) /', $notification['email_to'] );
1917 } else {
1918 $atts['email_to'] = array();
1919 }
1920
1921 if ( isset( $notification['also_email_to'] ) ) {
1922 $email_fields = (array) $notification['also_email_to'];
1923 $atts['email_to'] = array_merge( $email_fields, $atts['email_to'] );
1924 unset( $email_fields );
1925 }
1926
1927 foreach ( $atts['email_to'] as $key => $email_field ) {
1928
1929 if ( is_numeric( $email_field ) ) {
1930 $atts['email_to'][ $key ] = '[' . $email_field . ']';
1931 }
1932
1933 if ( strpos( $email_field, '|' ) ) {
1934 $email_opt = explode( '|', $email_field );
1935 if ( isset( $email_opt[0] ) ) {
1936 $atts['email_to'][ $key ] = '[' . $email_opt[0] . ' show=' . $email_opt[1] . ']';
1937 }
1938 unset( $email_opt );
1939 }
1940 }
1941 $atts['email_to'] = implode( ', ', $atts['email_to'] );
1942 }
1943
1944 private static function setup_new_notification( &$new_notification, $notification, $atts ) {
1945 // Set up new notification
1946 $new_notification = array(
1947 'post_content' => array(
1948 'email_to' => $atts['email_to'],
1949 'event' => $atts['event'],
1950 ),
1951 'post_name' => $atts['form_id'] . '_email_' . $atts['email_key'],
1952 );
1953
1954 // Add more fields to the new notification
1955 $add_fields = array( 'email_message', 'email_subject', 'plain_text', 'inc_user_info', 'conditions' );
1956 foreach ( $add_fields as $add_field ) {
1957 if ( isset( $notification[ $add_field ] ) ) {
1958 $new_notification['post_content'][ $add_field ] = $notification[ $add_field ];
1959 } elseif ( in_array( $add_field, array( 'plain_text', 'inc_user_info' ) ) ) {
1960 $new_notification['post_content'][ $add_field ] = 0;
1961 } else {
1962 $new_notification['post_content'][ $add_field ] = '';
1963 }
1964 unset( $add_field );
1965 }
1966
1967 // Set reply to
1968 $new_notification['post_content']['reply_to'] = $atts['reply_to'];
1969
1970 // Set from
1971 if ( ! empty( $atts['reply_to'] ) || ! empty( $atts['reply_to_name'] ) ) {
1972 $new_notification['post_content']['from'] = ( empty( $atts['reply_to_name'] ) ? '[sitename]' : $atts['reply_to_name'] ) . ' <' . ( empty( $atts['reply_to'] ) ? '[admin_email]' : $atts['reply_to'] ) . '>';
1973 }
1974 }
1975
1976 /**
1977 * Switch field IDs in pre-2.0 email conditional logic
1978 *
1979 * @param $post_content array, pass by reference
1980 */
1981 private static function switch_email_condition_field_ids( &$post_content ) {
1982 // Switch field IDs in conditional logic
1983 if ( isset( $post_content['conditions'] ) && is_array( $post_content['conditions'] ) ) {
1984 foreach ( $post_content['conditions'] as $email_key => $val ) {
1985 if ( is_numeric( $email_key ) ) {
1986 $post_content['conditions'][ $email_key ] = self::switch_action_field_ids( $val, array( 'hide_field' ) );
1987 }
1988 unset( $email_key, $val );
1989 }
1990 }
1991 }
1992
1993 private static function migrate_autoresponder_to_action( $form_options, $form_id, &$notifications ) {
1994 if ( isset( $form_options['auto_responder'] ) && $form_options['auto_responder'] && isset( $form_options['ar_email_message'] ) && $form_options['ar_email_message'] ) {
1995 // migrate autoresponder
1996
1997 $email_field = isset( $form_options['ar_email_to'] ) ? $form_options['ar_email_to'] : 0;
1998 if ( strpos( $email_field, '|' ) ) {
1999 // data from entries field
2000 $email_field = explode( '|', $email_field );
2001 if ( isset( $email_field[1] ) ) {
2002 $email_field = $email_field[1];
2003 }
2004 }
2005 if ( is_numeric( $email_field ) && ! empty( $email_field ) ) {
2006 $email_field = '[' . $email_field . ']';
2007 }
2008
2009 $notification = $form_options;
2010 $new_notification2 = array(
2011 'post_content' => array(
2012 'email_message' => $notification['ar_email_message'],
2013 'email_subject' => isset( $notification['ar_email_subject'] ) ? $notification['ar_email_subject'] : '',
2014 'email_to' => $email_field,
2015 'plain_text' => isset( $notification['ar_plain_text'] ) ? $notification['ar_plain_text'] : 0,
2016 'inc_user_info' => 0,
2017 ),
2018 'post_name' => $form_id . '_email_' . count( $notifications ),
2019 );
2020
2021 $reply_to = isset( $notification['ar_reply_to'] ) ? $notification['ar_reply_to'] : '';
2022 $reply_to_name = isset( $notification['ar_reply_to_name'] ) ? $notification['ar_reply_to_name'] : '';
2023
2024 if ( ! empty( $reply_to ) ) {
2025 $new_notification2['post_content']['reply_to'] = $reply_to;
2026 }
2027
2028 if ( ! empty( $reply_to ) || ! empty( $reply_to_name ) ) {
2029 $new_notification2['post_content']['from'] = ( empty( $reply_to_name ) ? '[sitename]' : $reply_to_name ) . ' <' . ( empty( $reply_to ) ? '[admin_email]' : $reply_to ) . '>';
2030 }
2031
2032 $notifications[] = $new_notification2;
2033 unset( $new_notification2 );
2034 }
2035 }
2036
2037 /**
2038 * PHP 8 backward compatibility for the libxml_disable_entity_loader function
2039 *
2040 * @param boolean $disable
2041 *
2042 * @return boolean
2043 */
2044 public static function maybe_libxml_disable_entity_loader( $loader ) {
2045 if ( version_compare( phpversion(), '8.0', '<' ) && function_exists( 'libxml_disable_entity_loader' ) ) {
2046 $loader = libxml_disable_entity_loader( $loader ); // phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated
2047 }
2048
2049 return $loader;
2050 }
2051
2052 public static function check_if_libxml_disable_entity_loader_exists() {
2053 return version_compare( phpversion(), '8.0', '<' ) && ! function_exists( 'libxml_disable_entity_loader' );
2054 }
2055 }
2056
2057