PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.0
2.13.0 2.13.1 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 All 80 releases
← All changes | includes/import/wp-import.php +1503 -0 2.7.72.13.0 View file →
@@ -1,0 +1,1503 @@
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 +}