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

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

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