PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / trunk
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder vtrunk
2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 All 78 releases
ablocks / includes / import / wp-import.php

wp-import.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder trunk, at includes/import/wp-import.php

1,504 lines 47.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ABlocks\import;
4
5 use ABlocks\Admin\Settings\Base;
6 use ABlocks\Helper;
7 use ABlocks\Classes\GlobalClasses;
8 use ABlocks\import\XmlParsers\WXR_Parser;
9 use WP_Error;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * WordPress importer class.
17 */
18 class WP_Import extends \WP_Importer {
19
20 public $id;
21
22 public $version;
23 public $authors = array();
24 public $posts = array();
25 public $terms = array();
26 public $categories = array();
27 public $tags = array();
28 public $base_url = '';
29 public $show_on_front = 'posts';
30 public $page_on_front = 0;
31 public $page_for_posts = 0;
32 public $storeengine_pages = [];
33 public $academy_pages = [];
34 public $ablocks_pages = [];
35 public $ablocks_options = [];
36
37 public $processed_authors = array();
38 public $author_mapping = array();
39 public $processed_terms = array();
40 public $processed_posts = array();
41 public $post_orphans = array();
42 public $processed_menu_items = array();
43 public $menu_item_orphans = array();
44 public $missing_menu_items = array();
45
46 public $fetch_attachments = true;
47 public $url_remap = array();
48 public $featured_images = array();
49
50 /**
51 * The main controller for the actual import stage.
52 *
53 * @param string $file Path to the WXR file for importing.
54 * @param bool $with_attachments with attachments ?.
55 */
56 public function import( string $file, bool $with_attachments = true ) {
57 add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) );
58 add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
59
60 $this->fetch_attachments = $with_attachments;
61
62 $result = $this->import_start( $file );
63 if ( is_wp_error( $result ) ) {
64 return $result;
65 }
66
67 wp_suspend_cache_invalidation();
68 $this->process_categories();
69 $this->process_tags();
70 $this->process_terms();
71 $this->process_posts();
72 wp_suspend_cache_invalidation( false );
73
74 // update incorrect/missing information in the DB
75 $this->backfill_parents();
76 $this->backfill_attachment_urls();
77 $this->remap_featured_images();
78 $this->save_global_settings();
79
80 $this->import_end();
81
82 return true;
83 }
84
85 /**
86 * Parses the WXR file and prepares us for the task of processing parsed data
87 *
88 * @param string $file Path to the WXR file for importing.
89 */
90 public function import_start( string $file ) {
91 if ( ! is_file( $file ) ) {
92 return new WP_Error( 'file_not_found', __( 'File not found', 'ablocks' ) );
93 }
94
95 Helper::emit_sse_message( [
96 'action' => 'log',
97 'message' => __( 'Import process started...', 'ablocks' )
98 ] );
99
100 $import_data = $this->parse( $file );
101
102 if ( is_wp_error( $import_data ) ) {
103 return $import_data;
104 }
105
106 $this->version = $import_data['version'];
107 $this->get_authors_from_import( $import_data );
108 $this->posts = $import_data['posts'];
109 $this->terms = $import_data['terms'];
110 $this->categories = $import_data['categories'];
111 $this->tags = $import_data['tags'];
112 $this->base_url = esc_url( $import_data['base_url'] );
113 $this->show_on_front = $import_data['show_on_front'];
114 $this->page_on_front = $import_data['page_on_front'];
115 $this->page_for_posts = $import_data['page_for_posts'];
116 $this->storeengine_pages = $import_data['storeengine_pages'];
117 $this->academy_pages = $import_data['academy_pages'];
118 $this->ablocks_pages = $import_data['ablocks_pages'];
119 $this->ablocks_options = $import_data['ablocks_options'];
120
121 wp_defer_term_counting( true );
122 wp_defer_comment_counting( true );
123
124 do_action( 'ablocks/template_import_start' );
125
126 return true;
127 }
128
129 /**
130 * Performs post-import cleanup of files and the cache
131 */
132 public function import_end() {
133 wp_import_cleanup( $this->id );
134
135 wp_cache_flush();
136 foreach ( get_taxonomies() as $tax ) {
137 delete_option( "{$tax}_children" );
138 _get_term_hierarchy( $tax );
139 }
140
141 wp_defer_term_counting( false );
142 wp_defer_comment_counting( false );
143
144 do_action( 'ablocks/template_import_end' );
145
146 Helper::emit_sse_message( [
147 'action' => 'log',
148 'level' => 'info',
149 'message' => __( 'Import process Finished.', 'ablocks' ),
150 ] );
151 }
152
153 /**
154 * Handles the WXR upload and initial parsing of the file to prepare for
155 * displaying author import options
156 *
157 * @return bool False if error uploading or invalid file, true otherwise
158 */
159 public function handle_upload(): bool {
160 $file = wp_import_handle_upload();
161
162 if ( isset( $file['error'] ) || ! file_exists( $file['file'] ) ) {
163 return false;
164 }
165
166 $this->id = (int) $file['id'];
167 $import_data = $this->parse( $file['file'] );
168 if ( is_wp_error( $import_data ) ) {
169 return false;
170 }
171
172 $this->version = $import_data['version'];
173 $this->get_authors_from_import( $import_data );
174
175 return true;
176 }
177
178 /**
179 * Retrieve authors from parsed WXR data
180 *
181 * Uses the provided author information from WXR 1.1 files
182 * or extracts info from each post for WXR 1.0 files
183 *
184 * @param array $import_data Data returned by a WXR parser.
185 */
186 public function get_authors_from_import( $import_data ) {
187 if ( ! empty( $import_data['authors'] ) ) {
188 $this->authors = $import_data['authors'];
189 // no author information, grab it from the posts
190 } else {
191 foreach ( $import_data['posts'] as $post ) {
192 $login = sanitize_user( $post['post_author'], true );
193 if ( empty( $login ) ) {
194 continue;
195 }
196
197 if ( ! isset( $this->authors[ $login ] ) ) {
198 $this->authors[ $login ] = array(
199 'author_login' => $login,
200 'author_display_name' => $post['post_author'],
201 );
202 }
203 }
204 }
205 }
206
207 /**
208 * Create new categories based on import information
209 *
210 * Doesn't create a new category if its slug already exists
211 */
212 public function process_categories() {
213 $this->categories = apply_filters( 'ablocks/template_import_categories', $this->categories );
214
215 if ( empty( $this->categories ) ) {
216 return;
217 }
218
219 foreach ( $this->categories as $cat ) {
220 // if the category already exists leave it alone
221 $term_id = term_exists( $cat['category_nicename'], 'category' );
222 if ( $term_id ) {
223 if ( is_array( $term_id ) ) {
224 $term_id = $term_id['term_id'];
225 }
226 if ( isset( $cat['term_id'] ) ) {
227 $this->processed_terms[ intval( $cat['term_id'] ) ] = (int) $term_id;
228 }
229 continue;
230 }
231
232 $parent = empty( $cat['category_parent'] ) ? 0 : category_exists( $cat['category_parent'] );
233 $description = isset( $cat['category_description'] ) ? $cat['category_description'] : '';
234
235 $data = array(
236 'category_nicename' => $cat['category_nicename'],
237 'category_parent' => $parent,
238 'cat_name' => wp_slash( $cat['cat_name'] ),
239 'category_description' => wp_slash( $description ),
240 );
241
242 $id = wp_insert_category( $data, true );
243 if ( ! is_wp_error( $id ) && $id > 0 ) {
244 if ( isset( $cat['term_id'] ) ) {
245 $this->processed_terms[ intval( $cat['term_id'] ) ] = $id;
246 }
247 } else {
248 continue;
249 }
250
251 $this->process_termmeta( $cat, $id );
252 }//end foreach
253
254 Helper::emit_sse_message( [
255 'action' => 'log',
256 'level' => 'info',
257 'message' => __( 'Categories imported', 'ablocks' ),
258 ] );
259
260 unset( $this->categories );
261 }
262
263 /**
264 * Create new post tags based on import information
265 *
266 * Doesn't create a tag if its slug already exists
267 */
268 public function process_tags() {
269 $this->tags = apply_filters( 'ablocks/template_import_tags', $this->tags );
270
271 if ( empty( $this->tags ) ) {
272 return;
273 }
274
275 foreach ( $this->tags as $tag ) {
276 // if the tag already exists leave it alone
277 $term_id = term_exists( $tag['tag_slug'], 'post_tag' );
278 if ( $term_id ) {
279 if ( is_array( $term_id ) ) {
280 $term_id = $term_id['term_id'];
281 }
282 if ( isset( $tag['term_id'] ) ) {
283 $this->processed_terms[ intval( $tag['term_id'] ) ] = (int) $term_id;
284 }
285 continue;
286 }
287
288 $description = isset( $tag['tag_description'] ) ? $tag['tag_description'] : '';
289 $args = array(
290 'slug' => $tag['tag_slug'],
291 'description' => wp_slash( $description ),
292 );
293
294 $id = wp_insert_term( wp_slash( $tag['tag_name'] ), 'post_tag', $args );
295 if ( ! is_wp_error( $id ) ) {
296 if ( isset( $tag['term_id'] ) ) {
297 $this->processed_terms[ intval( $tag['term_id'] ) ] = $id['term_id'];
298 }
299 } else {
300 continue;
301 }
302
303 $this->process_termmeta( $tag, $id['term_id'] );
304 }//end foreach
305
306 Helper::emit_sse_message( [
307 'action' => 'log',
308 'level' => 'info',
309 'message' => __( 'Tags imported', 'ablocks' ),
310 ] );
311
312 unset( $this->tags );
313 }
314
315 /**
316 * Create new terms based on import information
317 *
318 * Doesn't create a term its slug already exists
319 */
320 public function process_terms() {
321 $this->terms = apply_filters( 'ablocks/template_import_terms', $this->terms );
322
323 if ( empty( $this->terms ) ) {
324 return;
325 }
326
327 foreach ( $this->terms as $term ) {
328 // if the term already exists in the correct taxonomy leave it alone
329 $term_id = term_exists( $term['slug'], $term['term_taxonomy'] );
330 if ( $term_id ) {
331 if ( is_array( $term_id ) ) {
332 $term_id = $term_id['term_id'];
333 }
334 if ( isset( $term['term_id'] ) ) {
335 $this->processed_terms[ intval( $term['term_id'] ) ] = (int) $term_id;
336 }
337 continue;
338 }
339
340 if ( empty( $term['term_parent'] ) ) {
341 $parent = 0;
342 } else {
343 $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] );
344 if ( is_array( $parent ) ) {
345 $parent = $parent['term_id'];
346 }
347 }
348
349 $description = isset( $term['term_description'] ) ? $term['term_description'] : '';
350 $args = array(
351 'slug' => $term['slug'],
352 'description' => wp_slash( $description ),
353 'parent' => (int) $parent,
354 );
355
356 $id = wp_insert_term( wp_slash( $term['term_name'] ), $term['term_taxonomy'], $args );
357 if ( ! is_wp_error( $id ) ) {
358 if ( isset( $term['term_id'] ) ) {
359 $this->processed_terms[ intval( $term['term_id'] ) ] = $id['term_id'];
360 }
361 } else {
362 continue;
363 }
364
365 $this->process_termmeta( $term, $id['term_id'] );
366 }//end foreach
367
368 Helper::emit_sse_message( [
369 'action' => 'log',
370 'level' => 'info',
371 'message' => __( 'Terms imported', 'ablocks' ),
372 ] );
373
374 unset( $this->terms );
375 }
376
377 /**
378 * Add metadata to imported term.
379 *
380 * @param array $term Term data from WXR import.
381 * @param int $term_id ID of the newly created term.
382 *
383 * @since 0.6.2
384 */
385 protected function process_termmeta( $term, $term_id ) {
386 if ( ! isset( $term['termmeta'] ) ) {
387 $term['termmeta'] = array();
388 }
389
390 /**
391 * Filters the metadata attached to an imported term.
392 *
393 * @param array $termmeta Array of term meta.
394 * @param int $term_id ID of the newly created term.
395 * @param array $term Term data from the WXR import.
396 *
397 * @since 0.6.2
398 */
399 $term['termmeta'] = apply_filters( 'ablocks/template_import_term_meta', $term['termmeta'], $term_id, $term );
400
401 if ( empty( $term['termmeta'] ) ) {
402 return;
403 }
404
405 foreach ( $term['termmeta'] as $meta ) {
406 /**
407 * Filters the meta key for an imported piece of term meta.
408 *
409 * @param string $meta_key Meta key.
410 * @param int $term_id ID of the newly created term.
411 * @param array $term Term data from the WXR import.
412 *
413 * @since 0.6.2
414 */
415 $key = apply_filters( 'import_term_meta_key', $meta['key'], $term_id, $term );
416 if ( ! $key ) {
417 continue;
418 }
419
420 // Export gets meta straight from the DB so could have a serialized string
421 $value = maybe_unserialize( $meta['value'] );
422
423 add_term_meta( $term_id, wp_slash( $key ), wp_slash_strings_only( $value ) );
424
425 /**
426 * Fires after term meta is imported.
427 *
428 * @param int $term_id ID of the newly created term.
429 * @param string $key Meta key.
430 * @param mixed $value Meta value.
431 *
432 * @since 0.6.2
433 */
434 do_action( 'import_term_meta', $term_id, $key, $value );
435 }//end foreach
436 }
437
438 /**
439 * Create new posts based on import information
440 *
441 * Posts marked as having a parent which doesn't exist will become top level items.
442 * Doesn't create a new post if: the post type doesn't exist, the given post ID
443 * is already noted as imported or a post with the same title and date already exists.
444 * Note that new/updated terms, comments and meta are imported for the last of the above.
445 */
446 public function process_posts() {
447 $this->posts = apply_filters( 'ablocks/template_import_posts', $this->posts );
448
449 foreach ( $this->posts as $post ) {
450 $post = apply_filters( 'ablocks/template_import_post_data_raw', $post );
451
452 if ( ! post_type_exists( $post['post_type'] ) ) {
453 do_action( 'ablocks/template_import_post_exists', $post );
454 continue;
455 }
456
457 if ( isset( $this->processed_posts[ $post['post_id'] ] ) && ! empty( $post['post_id'] ) ) {
458 continue;
459 }
460
461 if ( 'auto-draft' === $post['status'] ) {
462 continue;
463 }
464
465 if ( 'nav_menu_item' === $post['post_type'] ) {
466 $this->process_menu_item( $post );
467 continue;
468 }
469
470 $post_exists = post_exists( $post['post_title'], '', '', $post['post_type'] );
471 if ( $post_exists && in_array( $post['post_type'], [ 'wp_template', 'wp_template_part' ], true ) ) {
472 // remove existence template.
473 wp_delete_post( $post_exists, true );
474 $post_exists = false;
475 }
476
477 /**
478 * Filter ID of the existing post corresponding to post currently importing.
479 *
480 * Return 0 to force the post to be imported. Filter the ID to be something else
481 * to override which existing post is mapped to the imported post.
482 *
483 * @param int $post_exists Post ID, or 0 if post did not exist.
484 * @param array $post The post array to be inserted.
485 *
486 * @see post_exists()
487 * @since 0.6.2
488 */
489 $post_exists = apply_filters( 'ablocks/template_import_existing_post', $post_exists, $post );
490
491 if ( ! $this->check_for_known_page( $post ) && $post_exists ) {
492 $comment_post_id = $post_exists;
493 $post_id = $post_exists;
494 $this->processed_posts[ intval( $post['post_id'] ) ] = intval( $post_exists );
495 } else {
496 $post_parent = (int) $post['post_parent'];
497 if ( $post_parent ) {
498 // if we already know the parent, map it to the new local ID
499 if ( isset( $this->processed_posts[ $post_parent ] ) ) {
500 $post_parent = $this->processed_posts[ $post_parent ];
501 // otherwise record the parent for later
502 } else {
503 $this->post_orphans[ intval( $post['post_id'] ) ] = $post_parent;
504 $post_parent = 0;
505 }
506 }
507
508 // map the post author
509 $author = sanitize_user( $post['post_author'], true );
510 if ( isset( $this->author_mapping[ $author ] ) ) {
511 $author = $this->author_mapping[ $author ];
512 } else {
513 $author = (int) get_current_user_id();
514 }
515
516 $postdata = array(
517 'import_id' => $post['post_id'],
518 'post_author' => $author,
519 'post_date' => $post['post_date'],
520 'post_date_gmt' => $post['post_date_gmt'],
521 'post_content' => $post['post_content'],
522 'post_excerpt' => $post['post_excerpt'],
523 'post_title' => $post['post_title'],
524 'post_status' => $post['status'],
525 'post_name' => $post['post_name'],
526 'comment_status' => $post['comment_status'],
527 'ping_status' => $post['ping_status'],
528 'guid' => $post['guid'],
529 'post_parent' => $post_parent,
530 'menu_order' => $post['menu_order'],
531 'post_type' => $post['post_type'],
532 'post_password' => $post['post_password'],
533 );
534
535 // remove existence global styles.
536 if ( 'wp_global_styles' === $post['post_type'] ) {
537 $this->remove_gutenberg_styles();
538 }
539
540 $original_post_id = $post['post_id'];
541 $postdata = apply_filters( 'ablocks/template_import_post_data_processed', $postdata, $post );
542
543 $postdata = wp_slash( $postdata );
544
545 if ( 'attachment' === $postdata['post_type'] && ! $post_exists ) {
546 if ( ! $this->fetch_attachments ) {
547 continue;
548 }
549 $remote_url = ! empty( $post['attachment_url'] ) ? $post['attachment_url'] : $post['guid'];
550
551 // try to use _wp_attached file for upload folder placement to ensure the same location as the export site
552 // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload()
553 $postdata['upload_date'] = $post['post_date'];
554 if ( isset( $post['postmeta'] ) ) {
555 foreach ( $post['postmeta'] as $meta ) {
556 if ( '_wp_attached_file' === $meta['key'] ) {
557 if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta['value'], $matches ) ) {
558 $postdata['upload_date'] = $matches[0];
559 }
560 break;
561 }
562 }
563 }
564
565 $comment_post_id = $this->process_attachment( $postdata, $remote_url );
566 $post_id = $comment_post_id;
567 } else {
568 if ( $post_exists ) {
569 $comment_post_id = wp_update_post( array_merge( $postdata, array( 'ID' => $post_exists ) ) );
570 } else {
571 $comment_post_id = wp_insert_post( $postdata, true );
572 }
573 $post_id = $comment_post_id;
574 do_action( 'ablocks/template_import_insert_post', $post_id, $original_post_id, $postdata, $post );
575 }//end if
576
577 if ( is_wp_error( $post_id ) ) {
578 Helper::emit_sse_message( [
579 'action' => 'log',
580 'level' => 'warning',
581 'message' => "Failed to import: {$post['post_type']} - {$post['post_title']}",
582 ] );
583 continue;
584 }
585
586 if ( 1 === (int) $post['is_sticky'] ) {
587 stick_post( $post_id );
588 }
589
590 Helper::emit_sse_message( [
591 'action' => 'log',
592 'level' => 'info',
593 'message' => "{$post['post_type']} - {$post['post_title']} imported.",
594 ] );
595 }//end if
596
597 // update reading settings.
598 if ( 'page' === $this->show_on_front && 'page' === $post['post_type'] ) {
599 update_option( 'show_on_front', 'page' );
600 if ( $this->page_on_front === $post['post_id'] ) {
601 update_option( 'page_on_front', $post_id );
602 Helper::emit_sse_message( [
603 'action' => 'log',
604 'level' => 'info',
605 'message' => __( 'Front page setting\'s updated.', 'ablocks' ),
606 ] );
607 } elseif ( $post['post_id'] === $this->page_for_posts ) {
608 update_option( 'page_for_posts', $post_id );
609 Helper::emit_sse_message( [
610 'action' => 'log',
611 'level' => 'info',
612 'message' => __( 'Posts page setting\'s updated.', 'ablocks' ),
613 ] );
614 }
615 }//end if
616
617 // update storeengine page settings
618 if ( ! empty( $this->storeengine_pages ) && class_exists( 'StoreEngine' ) ) {
619 $se_page = array_search( $post['post_id'], $this->storeengine_pages, true );
620 if ( $se_page ) {
621 \StoreEngine\Admin\Settings\Base::save_settings( [
622 $se_page => $post_id
623 ] );
624 }
625 }
626
627 // update academy page settings
628 if ( ! empty( $this->academy_pages ) && class_exists( 'Academy' ) ) {
629 $academy_page = array_search( $post['post_id'], $this->academy_pages, true );
630 if ( $academy_page ) {
631 \Academy\Admin\Settings\Base::save_settings( [
632 $academy_page => $post_id
633 ] );
634 }
635 }
636
637 // update ablock page settings
638 if ( ! empty( $this->ablocks_pages ) ) {
639 $ablocks_page = array_search( $post['post_id'], $this->ablocks_pages, true );
640 if ( $ablocks_page ) {
641 Base::save_settings( [
642 $ablocks_page => $post_id
643 ] );
644 }
645 }
646
647 // map pre-import ID to local ID
648 $this->processed_posts[ intval( $post['post_id'] ) ] = (int) $post_id;
649
650 if ( ! isset( $post['terms'] ) ) {
651 $post['terms'] = array();
652 }
653
654 $post['terms'] = apply_filters( 'ablocks/template_import_post_terms', $post['terms'], $post_id, $post );
655
656 // add categories, tags and other terms
657 if ( ! empty( $post['terms'] ) ) {
658 $terms_to_set = array();
659 foreach ( $post['terms'] as $term ) {
660 // back compat with WXR 1.0 map 'tag' to 'post_tag'
661 $taxonomy = ( 'tag' === $term['domain'] ) ? 'post_tag' : $term['domain'];
662 if ( 'wp_theme' === $taxonomy ) {
663 $term['name'] = wp_get_theme()->get_stylesheet();
664 $term['slug'] = wp_get_theme()->get_stylesheet();
665 }
666 $term_exists = term_exists( $term['slug'], $taxonomy );
667 $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists;
668
669 if ( ! $term_id ) {
670 $t = wp_insert_term( $term['name'], $taxonomy, array( 'slug' => $term['slug'] ) );
671 if ( ! is_wp_error( $t ) ) {
672 $term_id = $t['term_id'];
673 do_action( 'ablocks/template_import_insert_term', $t, $term, $post_id, $post );
674 } else {
675 do_action( 'ablocks/template_import_insert_term_failed', $t, $term, $post_id, $post );
676 continue;
677 }
678 }
679 $terms_to_set[ $taxonomy ][] = intval( $term_id );
680 }//end foreach
681
682 foreach ( $terms_to_set as $tax => $ids ) {
683 $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
684 do_action( 'ablocks/template_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $post );
685 }
686 unset( $post['terms'], $terms_to_set );
687 }//end if
688
689 if ( ! isset( $post['comments'] ) ) {
690 $post['comments'] = array();
691 }
692
693 $post['comments'] = apply_filters( 'ablocks/template_import_post_comments', $post['comments'], $post_id, $post );
694
695 // add/update comments
696 if ( ! empty( $post['comments'] ) ) {
697 $num_comments = 0;
698 $inserted_comments = array();
699 foreach ( $post['comments'] as $comment ) {
700 $comment_id = $comment['comment_id'];
701 $newcomments[ $comment_id ]['comment_post_ID'] = $comment_post_id;
702 $newcomments[ $comment_id ]['comment_author'] = $comment['comment_author'];
703 $newcomments[ $comment_id ]['comment_author_email'] = $comment['comment_author_email'];
704 $newcomments[ $comment_id ]['comment_author_IP'] = $comment['comment_author_IP'];
705 $newcomments[ $comment_id ]['comment_author_url'] = $comment['comment_author_url'];
706 $newcomments[ $comment_id ]['comment_date'] = $comment['comment_date'];
707 $newcomments[ $comment_id ]['comment_date_gmt'] = $comment['comment_date_gmt'];
708 $newcomments[ $comment_id ]['comment_content'] = $comment['comment_content'];
709 $newcomments[ $comment_id ]['comment_approved'] = $comment['comment_approved'];
710 $newcomments[ $comment_id ]['comment_type'] = $comment['comment_type'];
711 $newcomments[ $comment_id ]['comment_parent'] = $comment['comment_parent'];
712 $newcomments[ $comment_id ]['commentmeta'] = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : array();
713 if ( isset( $this->processed_authors[ $comment['comment_user_id'] ] ) ) {
714 $newcomments[ $comment_id ]['user_id'] = $this->processed_authors[ $comment['comment_user_id'] ];
715 }
716 }
717 ksort( $newcomments );
718
719 foreach ( $newcomments as $key => $comment ) {
720 // if this is a new post we can skip the comment_exists() check
721 if ( ! $post_exists || ! comment_exists( $comment['comment_author'], $comment['comment_date'] ) ) {
722 if ( isset( $inserted_comments[ $comment['comment_parent'] ] ) ) {
723 $comment['comment_parent'] = $inserted_comments[ $comment['comment_parent'] ];
724 }
725
726 $comment_data = wp_slash( $comment );
727 unset( $comment_data['commentmeta'] ); // Handled separately, wp_insert_comment() also expects `comment_meta`.
728 $comment_data = wp_filter_comment( $comment_data );
729
730 $inserted_comments[ $key ] = wp_insert_comment( $comment_data );
731
732 do_action( 'ablocks/template_import_insert_comment', $inserted_comments[ $key ], $comment, $comment_post_id, $post );
733
734 foreach ( $comment['commentmeta'] as $meta ) {
735 $value = maybe_unserialize( $meta['value'] );
736
737 add_comment_meta( $inserted_comments[ $key ], wp_slash( $meta['key'] ), wp_slash_strings_only( $value ) );
738 }
739
740 ++ $num_comments;
741 }//end if
742 }//end foreach
743 unset( $newcomments, $inserted_comments, $post['comments'] );
744 }//end if
745
746 if ( ! isset( $post['postmeta'] ) ) {
747 $post['postmeta'] = array();
748 }
749
750 $post['postmeta'] = apply_filters( 'ablocks/template_import_post_meta', $post['postmeta'], $post_id, $post );
751
752 // add/update post meta
753 if ( ! empty( $post['postmeta'] ) ) {
754 foreach ( $post['postmeta'] as $meta ) {
755 $key = apply_filters( 'import_post_meta_key', $meta['key'], $post_id, $post );
756 $value = false;
757
758 if ( '_edit_last' === $key ) {
759 if ( isset( $this->processed_authors[ intval( $meta['value'] ) ] ) ) {
760 $value = $this->processed_authors[ intval( $meta['value'] ) ];
761 } else {
762 $key = false;
763 }
764 }
765
766 if ( $key ) {
767 // export gets meta straight from the DB so could have a serialized string
768 if ( ! $value ) {
769 $value = maybe_unserialize( $meta['value'] );
770 }
771
772 add_post_meta( $post_id, wp_slash( $key ), wp_slash_strings_only( $value ) );
773
774 do_action( 'import_post_meta', $post_id, $key, $value );
775
776 // if the post has a featured image, take note of this in case of remap
777 if ( '_thumbnail_id' === $key ) {
778 $this->featured_images[ $post_id ] = (int) $value;
779 }
780 }
781 }//end foreach
782 }//end if
783 }//end foreach
784
785 unset( $this->posts );
786 }
787
788 private function remove_gutenberg_styles() {
789 $args = array(
790 'post_type' => 'wp_global_styles',
791 'post_status' => 'any',
792 'posts_per_page' => 10,
793 'fields' => 'ids',
794 );
795 $styles = get_posts( $args );
796 foreach ( $styles as $style ) {
797 wp_delete_post( $style );
798 }
799 Helper::emit_sse_message( [
800 'action' => 'log',
801 'level' => 'info',
802 'message' => __( 'Existence gutenberg styles removed.', 'ablocks' ),
803 ] );
804 }
805
806 private function check_for_known_page( array $post ) {
807 if ( ! empty( $this->storeengine_pages ) && class_exists( 'StoreEngine' ) ) {
808 return (bool) array_search( $post['post_id'], $this->storeengine_pages, true );
809 }
810
811 if ( ! empty( $this->academy_pages ) && class_exists( 'Academy' ) ) {
812 return (bool) array_search( $post['post_id'], $this->academy_pages, true );
813 }
814
815 if ( ! empty( $this->ablocks_pages ) ) {
816 return (bool) array_search( $post['post_id'], $this->ablocks_pages, true );
817 }
818
819 return false;
820 }
821
822 /**
823 * Attempt to create a new menu item from import data
824 *
825 * Fails for draft, orphaned menu items and those without an associated nav_menu
826 * or an invalid nav_menu term. If the post type or term object which the menu item
827 * represents doesn't exist then the menu item will not be imported (waits until the
828 * end of the import to retry again before discarding).
829 *
830 * @param array $item Menu item details from WXR file.
831 */
832 public function process_menu_item( $item ) {
833 // skip draft, orphaned menu items
834 if ( 'draft' === $item['status'] ) {
835 return;
836 }
837
838 $menu_slug = false;
839 if ( isset( $item['terms'] ) ) {
840 // loop through terms, assume first nav_menu term is correct menu
841 foreach ( $item['terms'] as $term ) {
842 if ( 'nav_menu' === $term['domain'] ) {
843 $menu_slug = $term['slug'];
844 break;
845 }
846 }
847 }
848
849 // no nav_menu term associated with this menu item
850 if ( ! $menu_slug ) {
851 return;
852 }
853
854 $menu_id = term_exists( $menu_slug, 'nav_menu' );
855 if ( ! $menu_id ) {
856 return;
857 } else {
858 $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
859 }
860
861 foreach ( $item['postmeta'] as $meta ) {
862 ${$meta['key']} = $meta['value'];
863 }
864
865 if ( 'taxonomy' === $_menu_item_type && isset( $this->processed_terms[ intval( $_menu_item_object_id ) ] ) ) {
866 $_menu_item_object_id = $this->processed_terms[ intval( $_menu_item_object_id ) ];
867 } elseif ( 'post_type' === $_menu_item_type && isset( $this->processed_posts[ intval( $_menu_item_object_id ) ] ) ) {
868 $_menu_item_object_id = $this->processed_posts[ intval( $_menu_item_object_id ) ];
869 } elseif ( 'custom' !== $_menu_item_type ) {
870 // associated object is missing or not imported yet, we'll retry later
871 $this->missing_menu_items[] = $item;
872
873 return;
874 }
875
876 if ( isset( $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ] ) ) {
877 $_menu_item_menu_item_parent = $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ];
878 } elseif ( $_menu_item_menu_item_parent ) {
879 $this->menu_item_orphans[ intval( $item['post_id'] ) ] = (int) $_menu_item_menu_item_parent;
880 $_menu_item_menu_item_parent = 0;
881 }
882
883 // wp_update_nav_menu_item expects CSS classes as a space separated string
884 $_menu_item_classes = maybe_unserialize( $_menu_item_classes );
885 if ( is_array( $_menu_item_classes ) ) {
886 $_menu_item_classes = implode( ' ', $_menu_item_classes );
887 }
888
889 $args = array(
890 'menu-item-object-id' => $_menu_item_object_id,
891 'menu-item-object' => $_menu_item_object,
892 'menu-item-parent-id' => $_menu_item_menu_item_parent,
893 'menu-item-position' => intval( $item['menu_order'] ),
894 'menu-item-type' => $_menu_item_type,
895 'menu-item-title' => $item['post_title'],
896 'menu-item-url' => $_menu_item_url,
897 'menu-item-description' => $item['post_content'],
898 'menu-item-attr-title' => $item['post_excerpt'],
899 'menu-item-target' => $_menu_item_target,
900 'menu-item-classes' => $_menu_item_classes,
901 'menu-item-xfn' => $_menu_item_xfn,
902 'menu-item-status' => $item['status'],
903 );
904
905 $id = wp_update_nav_menu_item( $menu_id, 0, $args );
906 if ( $id && ! is_wp_error( $id ) ) {
907 $this->processed_menu_items[ intval( $item['post_id'] ) ] = (int) $id;
908 }
909 }
910
911 /**
912 * If fetching attachments is enabled then attempt to create a new attachment
913 *
914 * @param array $post Attachment post details from WXR.
915 * @param string $url URL to fetch attachment from.
916 *
917 * @return int|WP_Error Post ID on success, WP_Error otherwise
918 */
919 public function process_attachment( $post, $url ) {
920 if ( ! $this->fetch_attachments ) {
921 return;
922 }
923
924 // if the URL is absolute, but does not contain address, then upload it assuming base_site_url
925 if ( preg_match( '|^/[\w\W]+$|', $url ) ) {
926 $url = rtrim( $this->base_url, '/' ) . $url;
927 }
928
929 $upload = $this->fetch_remote_file( $url, $post );
930 if ( is_wp_error( $upload ) ) {
931 return $upload;
932 }
933
934 $info = wp_check_filetype( $upload['file'] );
935 if ( $info ) {
936 $post['post_mime_type'] = $info['type'];
937 } else {
938 return new WP_Error( 'attachment_processing_error', __( 'Invalid file type', 'ablocks' ) );
939 }
940
941 $post['guid'] = $upload['url'];
942
943 // as per wp-admin/includes/upload.php
944 $post_id = wp_insert_attachment( $post, $upload['file'] );
945 wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
946
947 // remap resized image URLs, works by stripping the extension and remapping the URL stub.
948 if ( preg_match( '!^image/!', $info['type'] ) ) {
949 $parts = pathinfo( $url );
950 $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
951
952 $parts_new = pathinfo( $upload['url'] );
953 $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
954
955 $this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new;
956 }
957
958 return $post_id;
959 }
960
961 /**
962 * Attempt to download a remote file attachment
963 *
964 * @param string $url URL of item to fetch.
965 * @param array $post Attachment details.
966 *
967 * @return array|WP_Error Local file location details on success, WP_Error otherwise
968 */
969 public function fetch_remote_file( $url, $post ) {
970 // Extract the file name from the URL.
971 $path = wp_parse_url( $url, PHP_URL_PATH );
972 $file_name = '';
973 if ( is_string( $path ) ) {
974 $file_name = basename( $path );
975 }
976
977 if ( ! $file_name ) {
978 $file_name = md5( $url );
979 }
980
981 $tmp_file_name = wp_tempnam( $file_name );
982 if ( ! $tmp_file_name ) {
983 return new WP_Error( 'import_no_file', __( 'Could not create temporary file.', 'ablocks' ) );
984 }
985
986 // Fetch the remote URL and write it to the placeholder file.
987 $remote_response = wp_safe_remote_get(
988 $url,
989 array(
990 'timeout' => 300,
991 'stream' => true,
992 'filename' => $tmp_file_name,
993 'headers' => array(
994 'Accept-Encoding' => 'identity',
995 ),
996 )
997 );
998
999 if ( is_wp_error( $remote_response ) ) {
1000 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.unlink_unlink
1001 @unlink( $tmp_file_name );
1002
1003 return new WP_Error(
1004 'import_file_error',
1005 sprintf(
1006 /* translators: 1: The WordPress error message. 2: The WordPress error code. */
1007 __( 'Request failed due to an error: %1$s (%2$s)', 'ablocks' ),
1008 esc_html( $remote_response->get_error_message() ),
1009 esc_html( $remote_response->get_error_code() )
1010 )
1011 );
1012 }
1013
1014 $remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response );
1015
1016 // Make sure the fetch was successful.
1017 if ( 200 !== $remote_response_code ) {
1018 if ( file_exists( $tmp_file_name ) ) {
1019 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
1020 unlink( $tmp_file_name );
1021 }
1022
1023 return new WP_Error(
1024 'import_file_error',
1025 sprintf(
1026 /* translators: 1: The HTTP error message. 2: The HTTP error code. */
1027 __( 'Remote server returned the following unexpected result: %1$s (%2$s)', 'ablocks' ),
1028 get_status_header_desc( $remote_response_code ),
1029 esc_html( $remote_response_code )
1030 )
1031 );
1032 }
1033
1034 $headers = wp_remote_retrieve_headers( $remote_response );
1035
1036 // Request failed.
1037 if ( ! $headers ) {
1038 if ( file_exists( $tmp_file_name ) ) {
1039 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
1040 unlink( $tmp_file_name );
1041 }
1042
1043 return new WP_Error( 'import_file_error', __( 'Remote server did not respond', 'ablocks' ) );
1044 }
1045
1046 $filesize = (int) filesize( $tmp_file_name );
1047
1048 if ( 0 === $filesize ) {
1049 if ( file_exists( $tmp_file_name ) ) {
1050 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
1051 unlink( $tmp_file_name );
1052 }
1053
1054 return new WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'ablocks' ) );
1055 }
1056
1057 if ( ! isset( $headers['content-encoding'] ) && isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) {
1058 if ( file_exists( $tmp_file_name ) ) {
1059 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
1060 unlink( $tmp_file_name );
1061 }
1062
1063 return new WP_Error( 'import_file_error', __( 'Downloaded file has incorrect size', 'ablocks' ) );
1064 }
1065
1066 $max_size = (int) $this->max_attachment_size();
1067 if ( ! empty( $max_size ) && $filesize > $max_size ) {
1068 if ( file_exists( $tmp_file_name ) ) {
1069 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
1070 unlink( $tmp_file_name );
1071 }
1072
1073 // Translators: %s is the file size limit for the remote file import
1074 return new WP_Error( 'import_file_error', sprintf( __( 'Remote file is too large, limit is %s', 'ablocks' ), size_format( $max_size ) ) );
1075 }
1076
1077 // Override file name with Content-Disposition header value.
1078 if ( ! empty( $headers['content-disposition'] ) ) {
1079 $file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] );
1080 if ( $file_name_from_disposition ) {
1081 $file_name = $file_name_from_disposition;
1082 }
1083 }
1084
1085 // Set file extension if missing.
1086 $file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
1087 if ( ! $file_ext && ! empty( $headers['content-type'] ) ) {
1088 $extension = self::get_file_extension_by_mime_type( $headers['content-type'] );
1089 if ( $extension ) {
1090 $file_name = "{$file_name}.{$extension}";
1091 }
1092 }
1093
1094 // Handle the upload like _wp_handle_upload() does.
1095 $wp_filetype = wp_check_filetype_and_ext( $tmp_file_name, $file_name );
1096 $ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
1097 $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
1098 $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
1099
1100 // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
1101 if ( $proper_filename ) {
1102 $file_name = $proper_filename;
1103 }
1104
1105 if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
1106 return new WP_Error( 'import_file_error', __( 'Sorry, this file type is not permitted for security reasons.', 'ablocks' ) );
1107 }
1108
1109 $uploads = wp_upload_dir( $post['upload_date'] );
1110 if ( ! ( $uploads && false === $uploads['error'] ) ) {
1111 return new WP_Error( 'upload_dir_error', $uploads['error'] );
1112 }
1113
1114 // Move the file to the uploads dir.
1115 $file_name = wp_unique_filename( $uploads['path'], $file_name );
1116 $new_file = $uploads['path'] . "/$file_name";
1117 $move_new_file = copy( $tmp_file_name, $new_file );
1118
1119 if ( ! $move_new_file ) {
1120 if ( file_exists( $tmp_file_name ) ) {
1121 // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
1122 unlink( $tmp_file_name );
1123 }
1124
1125 return new WP_Error( 'import_file_error', __( 'The uploaded file could not be moved', 'ablocks' ) );
1126 }
1127
1128 // Set correct file permissions.
1129 $stat = stat( dirname( $new_file ) );
1130 $perms = $stat['mode'] & 0000666;
1131 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod
1132 chmod( $new_file, $perms );
1133
1134 $upload = array(
1135 'file' => $new_file,
1136 'url' => $uploads['url'] . "/$file_name",
1137 'type' => $wp_filetype['type'],
1138 'error' => false,
1139 );
1140
1141 // keep track of the old and new urls so we can substitute them later
1142 $this->url_remap[ $url ] = $upload['url'];
1143 $this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed?
1144 // keep track of the destination if the remote url is redirected somewhere else
1145 if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] !== $url ) {
1146 $this->url_remap[ $headers['x-final-location'] ] = $upload['url'];
1147 }
1148
1149 return $upload;
1150 }
1151
1152 /**
1153 * Attempt to associate posts and menu items with previously missing parents
1154 *
1155 * An imported post's parent may not have been imported when it was first created
1156 * so try again. Similarly, for child menu items and menu items which were missing
1157 * the object (e.g. post) they represent in the menu
1158 */
1159 public function backfill_parents() {
1160 global $wpdb;
1161
1162 // find parents for post orphans
1163 foreach ( $this->post_orphans as $child_id => $parent_id ) {
1164 $local_child_id = false;
1165 $local_parent_id = false;
1166 if ( isset( $this->processed_posts[ $child_id ] ) ) {
1167 $local_child_id = $this->processed_posts[ $child_id ];
1168 }
1169 if ( isset( $this->processed_posts[ $parent_id ] ) ) {
1170 $local_parent_id = $this->processed_posts[ $parent_id ];
1171 }
1172
1173 if ( $local_child_id && $local_parent_id ) {
1174 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1175 $wpdb->update( $wpdb->posts, array( 'post_parent' => $local_parent_id ), array( 'ID' => $local_child_id ), '%d', '%d' );
1176 clean_post_cache( $local_child_id );
1177 }
1178 }
1179
1180 // all other posts/terms are imported, retry menu items with missing associated object
1181 $missing_menu_items = $this->missing_menu_items;
1182 foreach ( $missing_menu_items as $item ) {
1183 $this->process_menu_item( $item );
1184 }
1185
1186 // find parents for menu item orphans
1187 foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
1188 $local_child_id = 0;
1189 $local_parent_id = 0;
1190 if ( isset( $this->processed_menu_items[ $child_id ] ) ) {
1191 $local_child_id = $this->processed_menu_items[ $child_id ];
1192 }
1193 if ( isset( $this->processed_menu_items[ $parent_id ] ) ) {
1194 $local_parent_id = $this->processed_menu_items[ $parent_id ];
1195 }
1196
1197 if ( $local_child_id && $local_parent_id ) {
1198 update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
1199 }
1200 }
1201 }
1202
1203 /**
1204 * Use stored mapping information to update old attachment URLs
1205 */
1206 public function backfill_attachment_urls() {
1207 global $wpdb;
1208 // make sure we do the longest urls first, in case one is a substring of another
1209 uksort( $this->url_remap, array( &$this, 'cmpr_strlen' ) );
1210
1211 foreach ( $this->url_remap as $from_url => $to_url ) {
1212 // remap urls in post_content
1213 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1214 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ) );
1215 // remap enclosure urls
1216 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1217 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ) );
1218 }
1219
1220 if ( ! empty( $this->url_remap ) ) {
1221 Helper::emit_sse_message( [
1222 'action' => 'log',
1223 'level' => 'info',
1224 'message' => __( 'Old attachment URLs have been updated.', 'ablocks' ),
1225 ] );
1226 }
1227 }
1228
1229 /**
1230 * Update _thumbnail_id meta to new, imported attachment IDs
1231 */
1232 public function remap_featured_images() {
1233 // cycle through posts that have a featured image
1234 foreach ( $this->featured_images as $post_id => $value ) {
1235 if ( isset( $this->processed_posts[ $value ] ) ) {
1236 $new_id = $this->processed_posts[ $value ];
1237 // only update if there's a difference
1238 if ( $new_id !== $value ) {
1239 update_post_meta( $post_id, '_thumbnail_id', $new_id );
1240 }
1241 }
1242 }
1243 Helper::emit_sse_message( [
1244 'action' => 'log',
1245 'level' => 'info',
1246 'message' => __( 'Imported posts `_thumbnail_id` updated.', 'ablocks' ),
1247 ] );
1248 }
1249
1250 public function save_global_settings() {
1251 if ( ! empty( $this->ablocks_options ) ) {
1252 $settings = $this->ablocks_options;
1253 if ( ! empty( $settings['frontend_dashboard_sub_pages'] ) ) {
1254 update_option( 'ablocks_frontend_dashboard_sub_pages', wp_json_encode( $settings['frontend_dashboard_sub_pages'] ) );
1255 unset( $settings['frontend_dashboard_sub_pages'] );
1256 }
1257
1258 if ( isset( $settings['global_classes'] ) ) {
1259 $this->import_global_classes( $settings['global_classes'] );
1260 unset( $settings['global_classes'] );
1261 }
1262
1263 Base::save_settings( $settings );
1264 }
1265 }
1266
1267 /**
1268 * Sanitise one imported class record.
1269 *
1270 * The file is untrusted input, so a record gets the same treatment
1271 * GlobalClasses::upsert() gives one saved from the editor: the id through
1272 * `sanitize_html_class` so it cannot escape the selector it is written
1273 * into, and the legacy raw `css` declaration string stripped of braces and
1274 * angle brackets so it cannot close its own rule and open another.
1275 *
1276 * @param array $class One record from the imported library.
1277 * @return array The sanitised record.
1278 */
1279 private function sanitize_global_class( $class ) {
1280 $clean = [
1281 'id' => sanitize_html_class( $class['id'] ),
1282 'label' => isset( $class['label'] ) ? sanitize_text_field( $class['label'] ) : '',
1283 ];
1284
1285 if ( isset( $class['styles'] ) && is_array( $class['styles'] ) ) {
1286 // Structured buckets are compiled through the styles schema, which
1287 // only emits properties it knows, so they need no extra stripping.
1288 $clean['styles'] = $class['styles'];
1289 } elseif ( isset( $class['css'] ) ) {
1290 $clean['css'] = trim( preg_replace( '/[{}<>]/', '', (string) $class['css'] ) );
1291 }
1292
1293 return $clean;
1294 }
1295
1296 /**
1297 * Merge the imported global-class library into this site's.
1298 *
1299 * A class already on this site wins: overwriting it would silently restyle
1300 * blocks that are already published here, which is a worse surprise than an
1301 * imported block keeping the local definition of a class it names. Skipped
1302 * ids are reported so the collision is visible rather than guessed at.
1303 *
1304 * @param array $incoming Class records from the imported file.
1305 */
1306 private function import_global_classes( $incoming ) {
1307 if ( ! is_array( $incoming ) || empty( $incoming ) ) {
1308 return;
1309 }
1310
1311 $existing = GlobalClasses::get_all();
1312 $known = [];
1313 foreach ( $existing as $class ) {
1314 if ( ! empty( $class['id'] ) ) {
1315 $known[ $class['id'] ] = true;
1316 }
1317 }
1318
1319 $added = [];
1320 $skipped = [];
1321 foreach ( $incoming as $class ) {
1322 if ( empty( $class['id'] ) || '' === sanitize_html_class( $class['id'] ) ) {
1323 continue;
1324 }
1325 if ( isset( $known[ $class['id'] ] ) ) {
1326 $skipped[] = $class['id'];
1327 continue;
1328 }
1329 $existing[] = $this->sanitize_global_class( $class );
1330 $added[] = $class['id'];
1331 }
1332
1333 if ( ! empty( $added ) ) {
1334 update_option( 'ablocks_global_classes', wp_json_encode( array_values( $existing ) ), false );
1335 GlobalClasses::bump_revision();
1336 }
1337
1338 Helper::emit_sse_message( [
1339 'action' => 'log',
1340 'level' => 'info',
1341 'message' => sprintf(
1342 /* translators: 1: number of imported global classes, 2: number skipped because the id already existed. */
1343 __( 'Global classes: %1$d imported, %2$d skipped (id already in use).', 'ablocks' ),
1344 count( $added ),
1345 count( $skipped )
1346 ),
1347 ] );
1348 }
1349
1350 /**
1351 * Parse a WXR file
1352 *
1353 * @param string $file Path to WXR file for parsing.
1354 *
1355 * @return array|WP_Error Information gathered from the WXR file.
1356 */
1357 public function parse( string $file ) {
1358 $parser = new WXR_Parser();
1359
1360 return $parser->parse( $file );
1361 }
1362
1363 /**
1364 * Decide if the given meta key maps to information we will want to import
1365 *
1366 * @param string $key The meta key to check.
1367 *
1368 * @return string|bool The key if we do want to import, false if not
1369 */
1370 public function is_valid_meta_key( $key ) {
1371 // skip attachment metadata since we'll regenerate it from scratch
1372 // skip _edit_lock as not relevant for import
1373 if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ), true ) ) {
1374 return false;
1375 }
1376
1377 return $key;
1378 }
1379
1380 /**
1381 * Decide what the maximum file size for downloaded attachments is.
1382 * Default is 0 (unlimited), can be filtered via import_attachment_size_limit
1383 *
1384 * @return int Maximum attachment file size to import
1385 */
1386 public function max_attachment_size(): int {
1387 return apply_filters( 'import_attachment_size_limit', 0 );
1388 }
1389
1390 /**
1391 * Added to http_request_timeout filter to force timeout at 60 seconds during import
1392 *
1393 * @param mixed $val Value.
1394 *
1395 * @return int 60
1396 */
1397 public function bump_request_timeout( $val ): int {
1398 return 60;
1399 }
1400
1401 public function cmpr_strlen( $a, $b ): int {
1402 // return the difference in length between two strings.
1403 return strlen( $b ) - strlen( $a );
1404 }
1405
1406 /**
1407 * Parses filename from a Content-Disposition header value.
1408 *
1409 * As per RFC6266:
1410 *
1411 * content-disposition = "Content-Disposition" ":"
1412 * disposition-type *( ";" disposition-parm )
1413 *
1414 * disposition-type = "inline" | "attachment" | disp-ext-type
1415 * ; case-insensitive
1416 * disp-ext-type = token
1417 *
1418 * disposition-parm = filename-parm | disp-ext-parm
1419 *
1420 * filename-parm = "filename" "=" value
1421 * | "filename*" "=" ext-value
1422 *
1423 * disp-ext-parm = token "=" value
1424 * | ext-token "=" ext-value
1425 * ext-token = <the characters in token, followed by "*">
1426 *
1427 * @param string[] $disposition_header List of Content-Disposition header values.
1428 *
1429 * @return string|null Filename if available, or null if not found.
1430 * @link http://tools.ietf.org/html/rfc2388
1431 * @link http://tools.ietf.org/html/rfc6266
1432 *
1433 * @since 0.7.0
1434 *
1435 * @see WP_REST_Attachments_Controller::get_filename_from_disposition()
1436 */
1437 protected static function get_filename_from_disposition( $disposition_header ) {
1438 // Get the filename.
1439 $filename = null;
1440
1441 foreach ( $disposition_header as $value ) {
1442 $value = trim( $value );
1443
1444 if ( strpos( $value, ';' ) === false ) {
1445 continue;
1446 }
1447
1448 list( $type, $attr_parts ) = explode( ';', $value, 2 );
1449
1450 $attr_parts = explode( ';', $attr_parts );
1451 $attributes = array();
1452
1453 foreach ( $attr_parts as $part ) {
1454 if ( strpos( $part, '=' ) === false ) {
1455 continue;
1456 }
1457
1458 list( $key, $value ) = explode( '=', $part, 2 );
1459
1460 $attributes[ trim( $key ) ] = trim( $value );
1461 }
1462
1463 if ( empty( $attributes['filename'] ) ) {
1464 continue;
1465 }
1466
1467 $filename = trim( $attributes['filename'] );
1468
1469 // Unquote quoted filename, but after trimming.
1470 if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, - 1, 1 ) === '"' ) {
1471 $filename = substr( $filename, 1, - 1 );
1472 }
1473 }//end foreach
1474
1475 return $filename;
1476 }
1477
1478 /**
1479 * Retrieves file extension by mime type.
1480 *
1481 * @param string $mime_type Mime type to search extension for.
1482 *
1483 * @return string|null File extension if available, or null if not found.
1484 * @since 0.7.0
1485 */
1486 protected static function get_file_extension_by_mime_type( $mime_type ) {
1487 static $map = null;
1488
1489 if ( is_array( $map ) ) {
1490 return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
1491 }
1492
1493 $mime_types = wp_get_mime_types();
1494 $map = array_flip( $mime_types );
1495
1496 // Some types have multiple extensions, use only the first one.
1497 foreach ( $map as $type => $extensions ) {
1498 $map[ $type ] = strtok( $extensions, '|' );
1499 }
1500
1501 return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
1502 }
1503 }
1504