PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.8.4
Kubio AI Page Builder v2.8.4
2.8.4 2.8.3 2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / lib / src / DemoSites / WXRImporter.php
kubio / lib / src / DemoSites Last commit date
DemoSites.php 1 year ago DemoSitesHelpers.php 1 month ago DemoSitesImportBlockMap.php 1 year ago DemoSitesImporter.php 1 year ago DemoSitesLogger.php 4 years ago DemoSitesRepository.php 1 year ago WXRExporter.php 1 year ago WXRImporter.php 1 year ago
WXRImporter.php
628 lines
1 <?php
2
3 namespace Kubio\DemoSites;
4
5
6 use IlluminateAgnostic\Arr\Support\Arr;
7 use Kubio\Core\Importer;
8 use ProteusThemes\WPContentImporter2\Importer as ProteusImporter;
9
10 class WXRImporter extends ProteusImporter {
11
12 const WXR_IMPORTER_TRANSIENT = 'kubio-wxr-importer-transient';
13 const EMAIL_REGEXP = "/(?:[a-z0-9!#$%&'*+=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/";
14 /**
15 * Time in milliseconds, marking the beginning of the import.
16 *
17 * @var float
18 */
19 private $start_time;
20
21 public function __construct( $options = array( 'is_stepped' => true ), $logger = null ) {
22
23 if ( ! defined( 'KUBIO_IS_STARTER_SITES_IMPORT' ) ) {
24 define( 'KUBIO_IS_STARTER_SITES_IMPORT', true );
25 }
26
27 add_filter( 'wxr_importer.pre_process.post', array( $this, 'preProcessPost' ), 10, 4 );
28 add_filter( 'wp_import_post_data_processed', array( $this, 'beforePostImport' ), 10, 1 );
29 add_filter( 'wp_import_post_terms', array( $this, 'importPostTerms' ), 10, 3 );
30 add_action( 'wp_import_insert_post', array( $this, 'afterPostImport' ), 10, 4 );
31
32 add_action( 'wxr_importer.pre_process.term', array( $this, 'beforeTermImport' ), 10, 2 );
33 add_action( 'wxr_importer.processed.term', array( $this, 'afterTermImport' ), 10, 2 );
34
35 DemoSitesImportBlockMap::init();
36
37 parent::__construct( $options, $logger );
38 }
39
40
41 public function updatePermalink() {
42 global $wp_rewrite;
43
44 $new_permalink_structure = '/%postname%/';
45 $current_permalink_structure = $wp_rewrite->permalink_structure;
46 if ( $new_permalink_structure === $current_permalink_structure ) {
47 return;
48 }
49 //Write the rule
50 $wp_rewrite->set_permalink_structure( $new_permalink_structure );
51
52 //Set the option
53 update_option( 'rewrite_rules', false );
54
55 //Flush the rules and tell it to write htaccess
56 $wp_rewrite->flush_rules( true );
57 }
58
59 public function preProcessPost( $data, $meta, $comments, $terms ) {
60
61 if ( $data['post_type'] === kubio_global_data_post_type() ) {
62
63 $current_global_data = kubio_global_data_post_id( true, true );
64
65 $ids = get_posts(
66 array(
67 'post_type' => kubio_global_data_post_type(),
68 'post_status' => array( 'publish' ),
69 'posts_per_page' => - 1,
70 'fields' => 'ids',
71 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
72 'exclude' => kubio_global_data_post_id(),
73 )
74 );
75
76 foreach ( $ids as $id ) {
77 if ( intval( $id ) !== intval( $current_global_data ) ) {
78 wp_delete_post( $id, true );
79 }
80 }
81
82 kubio_replace_global_data_content( $data['post_content'] );
83 return false;
84 }
85
86 if ( $data['post_type'] === 'wp_template' || $data['post_type'] === 'wp_template_part' ) {
87 // Skip imported template from twenty* themes
88 foreach ( $terms as $term ) {
89 $taxonomy = Arr::get( $term, 'taxonomy' );
90 $slug = Arr::get( $term, 'slug' );
91 if ( $taxonomy === 'wp_theme' && is_string( $slug ) && strpos( $slug, 'twenty' ) === 0 ) {
92 return false;
93 }
94 }
95 }
96
97 return $data;
98 }
99
100 public function beforePostImport( $postdata ) {
101
102 remove_filter( 'wp_insert_post_data', 'kubio_on_post_update', 10, 3 );
103 remove_action( 'wp_insert_post', 'kubio_update_meta', 10, 3 );
104 remove_filter( 'theme_mod_nav_menu_locations', 'kubio_nav_menu_locations_from_global_data' );
105
106 $content = $postdata['post_content'];
107 $content = str_replace( 'var(\\u002d\\u002d', 'var(--', $content );
108
109 $blocks = parse_blocks( $content );
110
111 if ( empty( $blocks ) ) {
112 return $postdata;
113 }
114
115 $blocks = kubio_blocks_update_template_parts_theme( $blocks, get_stylesheet() );
116 $blocks = kubio_blocks_update_block_links( $blocks, $this->base_url );
117
118 $has_blocks = false;
119
120 foreach ( $blocks as $block ) {
121 $has_blocks = $has_blocks || Arr::get( $block, 'blockName', null );
122 if ( $has_blocks ) {
123 break;
124 }
125 }
126
127 if ( ! $has_blocks ) {
128 return $postdata;
129 }
130
131 $this->blocksMapping( $postdata['import_id'], $blocks );
132
133 $blocks = Importer::maybeImportBlockAssets( $blocks, array( $this, 'requestNewAjaxCall' ) );
134
135 // WP_REST_Posts_Controller is adding slashes when saving content
136 $postdata['post_content'] = wp_slash( kubio_serialize_blocks( $blocks ) );
137
138 return $postdata;
139 }
140
141 private function blocksMapping( $import_id, $blocks ) {
142 foreach ( $blocks as $block ) {
143
144 if ( ! Arr::get( $block, 'blockName' ) ) {
145 continue;
146 }
147
148 $should_map = apply_filters( 'kubio/demo-import/should-map', false, $block['blockName'], $block );
149
150 if ( $should_map ) {
151 $this->setHasBlockMapping( $import_id, $block['blockName'] );
152 }
153
154 $this->blocksMapping( $import_id, $block['innerBlocks'] );
155 }
156 }
157
158 private function setHasBlockMapping( $import_id, $block_name ) {
159 Arr::set( $this->mapping, "blocks.{$import_id}.{$block_name}", true );
160 }
161
162 public function afterPostImport( $post_id, $original_id, $postdata, $data ) {
163
164 if ( Arr::get( $postdata, 'post_type', null ) === kubio_global_data_post_type() ) {
165 $global_data = json_decode( $postdata['post_content'], true );
166 Arr::forget( $global_data, 'menuLocations' );
167 wp_update_post(
168 array(
169 'ID' => $post_id,
170 'post_content' => json_encode( $global_data ),
171 )
172 );
173 }
174
175 $this->updatePermalink();
176 }
177
178 public function beforeTermImport( $data ) {
179
180 if ( $data['taxonomy'] === 'nav_menu' ) {
181 if ( strpos( strtolower( $data['name'] ), '[old]' ) !== false ) {
182 return false;
183
184 }
185 }
186
187 if ( $data['taxonomy'] === 'wp_theme' ) {
188 $theme = get_stylesheet();
189 return array(
190 'taxonomy' => 'wp_theme',
191 'slug' => $theme,
192 'name' => $theme,
193 );
194 }
195
196 return $data;
197 }
198
199 public function afterTermImport( $term_id, $data ) {
200 $original_id = isset( $data['id'] ) ? (int) $data['id'] : 0;
201
202 if ( $original_id && $data['taxonomy'] === 'nav_menu' ) {
203 Arr::set( $this->mapping, "menus_map.{$original_id}", (int) $term_id );
204 }
205 }
206
207 public function importPostTerms( $terms, $post_id, $data ) {
208
209 $post_type = Arr::get( $data, 'post_type', null );
210 $theme = get_stylesheet();
211
212 if ( $post_type && in_array( $post_type, array( 'wp_template', 'wp_template_part' ), true ) ) {
213
214 Arr::set( $this->requires_remapping, "post.{$post_id}", true );
215
216 foreach ( $terms as $index => $term ) {
217 if ( $term['taxonomy'] === 'wp_theme' ) {
218 array_splice( $terms, $index, 1 );
219 }
220 }
221
222 $terms[] = array(
223 'taxonomy' => 'wp_theme',
224 'slug' => $theme,
225 'name' => $theme,
226 );
227
228 $key = sha1( $term['taxonomy'] . ':' . $term['taxonomy'] );
229 Arr::forget( $this->mapping, "term.{$key}" );
230 }
231
232 return $terms;
233 }
234
235
236 /**
237 * Restore the importer data from the transient.
238 *
239 * @return boolean
240 */
241 public function restore_import_data_transient() {
242 if ( $data = get_transient( static::WXR_IMPORTER_TRANSIENT ) ) {
243 $this->options = empty( $data['options'] ) ? array() : $data['options'];
244 $this->mapping = empty( $data['mapping'] ) ? array() : $data['mapping'];
245 $this->requires_remapping = empty( $data['requires_remapping'] ) ? array() : $data['requires_remapping'];
246 $this->exists = empty( $data['exists'] ) ? array() : $data['exists'];
247 $this->user_slug_override = empty( $data['user_slug_override'] ) ? array() : $data['user_slug_override'];
248 $this->url_remap = empty( $data['url_remap'] ) ? array() : $data['url_remap'];
249 $this->featured_images = empty( $data['featured_images'] ) ? array() : $data['featured_images'];
250
251 return true;
252 }
253
254 return false;
255 }
256
257 public function delete_import_data_transient() {
258 delete_transient( static::WXR_IMPORTER_TRANSIENT );
259 }
260
261 public function import_content( $import_file ) {
262
263 if ( strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) === false ) {
264 // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
265 set_time_limit( 300 );
266 }
267
268 // Disable import of authors.
269 add_filter( 'wxr_importer.pre_process.user', '__return_false' );
270
271 // Check, if we need to send another AJAX request and set the importing author to the current user.
272 add_filter( 'wxr_importer.pre_process.post', array( $this, 'new_ajax_request_maybe' ) );
273
274 // Import content.
275 if ( ! empty( $import_file ) ) {
276 ob_start();
277 $this->import( $import_file );
278 $output = ob_get_clean();
279 }
280
281 return $this->logger->error_output;
282 }
283
284 /**
285 * The main controller for the actual import stage.
286 *
287 * @param string $file Path to the WXR file for importing.
288 * @param array $options Import options (which parts to import).
289 *
290 * @return boolean
291 */
292 public function import( $file, $options = array() ) {
293 // Start the import timer.
294 $this->start_time = microtime( true );
295
296 return parent::import( $file, $options );
297 }
298
299 /**
300 * Does the post exist?
301 *
302 * @param array $data Post data to check against.
303 *
304 * @return int|bool Existing post ID if it exists, false otherwise.
305 */
306 protected function post_exists( $data ) {
307 // Constant-time lookup if we prefilled
308 $exists_key = $data['guid'];
309
310 if ( empty( $exists_key ) ) {
311 $exists_key = $data['post_title'] . '___' . $data['post_type'];
312 }
313
314 if ( $this->options['prefill_existing_posts'] ) {
315 // OCDI: fix for custom post types. The guids in the prefilled section are escaped, so these ones should be as well.
316 $exists_key = htmlentities( $exists_key );
317
318 return isset( $this->exists['post'][ $exists_key ] ) ? $this->exists['post'][ $exists_key ] : false;
319 }
320
321 // No prefilling, but might have already handled it
322 if ( isset( $this->exists['post'][ $exists_key ] ) ) {
323 return $this->exists['post'][ $exists_key ];
324 }
325
326 if ( $data['post_type'] === 'attachment' && $data['guid'] ) {
327 if ( $imported_asset = Importer::getImportByGuid( $data['guid'] ) ) {
328 $this->exists['post'][ $exists_key ] = $imported_asset['id'];
329 }
330 }
331
332 // Still nothing, try post_exists, and cache it
333 $exists = post_exists( $data['post_title'], $data['post_content'], $data['post_date'] );
334 $this->exists['post'][ $exists_key ] = $exists;
335
336 return $exists;
337 }
338
339 protected function process_attachment( $post, $meta, $remote_url ) {
340
341 $imported = Importer::importRemoteFile( $remote_url );
342 $post_id = $imported['id'];
343
344 if ( $post_id === 0 ) {
345 // translators: %s file url
346 $error_message = sprintf( __( 'Unable to import remote file: %s', 'kubio' ), $remote_url );
347 $this->logger->warning( $error_message );
348
349 return new \WP_Error( 'attachment_processing_error', $error_message );
350 }
351
352 return $post_id;
353 }
354
355 /**
356 * Process and import post meta items.
357 *
358 * @param array $meta List of meta data arrays
359 * @param int $post_id Post to associate with
360 * @param array $post Post data
361 *
362 * @return int|WP_Error Number of meta items imported on success, error otherwise.
363 */
364 protected function process_post_meta( $meta, $post_id, $post ) {
365
366 // replace anchors urls
367 if ( Arr::get( $post, 'post_type' ) === 'nav_menu_item' ) {
368 foreach ( $meta as $index => $meta_item ) {
369 if ( $meta_item['key'] === '_menu_item_url' ) {
370 $value = $meta_item['value'];
371
372 $meta[ $index ] = array_merge(
373 $meta_item,
374 array(
375 'value' => str_replace( $this->base_url, site_url(), $value ),
376 )
377 );
378 break;
379 }
380 }
381 } else {
382 foreach ( $meta as $index => $meta_item ) {
383 $meta_item['value'] = maybe_serialize( $this->cleanupEmails( maybe_unserialize( $meta_item['value'] ) ) );
384 $meta[ $index ] = $meta_item;
385 }
386 }
387
388 if ( Arr::get( $post, 'post_type' ) === 'wp_template' || Arr::get( $post, 'post_type' ) === 'wp_template_part' ) {
389 $meta[] = array(
390 'key' => '_kubio_template_source',
391 'value' => 'kubio',
392 );
393 }
394
395 return parent::process_post_meta( $meta, $post_id, $post );
396 }
397
398
399 private function cleanupEmails( $content ) {
400 if ( is_array( $content ) ) {
401 $copy = $content;
402
403 array_walk_recursive(
404 $copy,
405 function ( &$value ) {
406 if ( is_string( $value ) ) {
407 $value = preg_replace( static::EMAIL_REGEXP, 'mail@example.com', $value );
408 }
409 }
410 );
411
412 return $copy;
413 }
414
415 return preg_replace( static::EMAIL_REGEXP, 'mail@example.com', $content );
416 }
417
418 protected function post_process() {
419 wp_cache_flush();
420 do_action( 'kubio/demo-site-import/post-process', $this );
421
422 $this->postProcessMenuItems();
423
424 parent::post_process();
425
426 // execute post process blocks after the template parts were set
427 $this->postProccessBlocks();
428 }
429
430 // update nav menu items parent meta value
431 protected function postProcessMenuItems() {
432 global $wpdb;
433 $post_mapping = $this->mapping['post'];
434
435 foreach ( $post_mapping as $original => $next_value ) {
436
437 $query = $wpdb->prepare(
438 "UPDATE {$wpdb->postmeta} SET meta_value = %d WHERE meta_key='_menu_item_menu_item_parent' AND meta_value= %d",
439 intval( $next_value ),
440 intval( $original )
441 );
442
443 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
444 $wpdb->query( $query );
445 }
446 }
447
448 protected function postProccessBlocks() {
449
450 $blocks_mapping = Arr::get( $this->mapping, 'blocks', array() );
451
452 foreach ( $blocks_mapping as $import_id => $mapped_blocks ) {
453 if ( $mapped_blocks !== null ) {
454
455 $post_id = Arr::get( $this->mapping, "post.{$import_id}", $import_id );
456
457 $post = get_post( $post_id );
458 if ( is_wp_error( $post ) ) {
459 DemoSitesHelpers::sendAjaxError( $post );
460 }
461
462 $blocks = parse_blocks( $post->post_content );
463
464 $blocks = $this->applyBlocksMapping( $blocks, $import_id );
465 $content = kubio_serialize_blocks( $blocks );
466
467 $result = wp_update_post(
468 wp_slash(
469 array(
470 'ID' => $post_id,
471 'post_content' => $content,
472 )
473 ),
474 true,
475 false
476 );
477
478 if ( is_wp_error( $result ) ) {
479 DemoSitesHelpers::sendAjaxError( $result );
480 }
481
482 Arr::set( $this->mapping, "blocks.{$import_id}", null );
483 $this->new_ajax_request_maybe();
484 }
485 }
486 }
487
488 private function applyBlocksMapping( $blocks, $import_id ) {
489
490 $mapped_blocks = Arr::get( $this->mapping, "blocks.{$import_id}", array() );
491
492 foreach ( $blocks as $index => $block ) {
493 $block_name = Arr::get( $block, 'blockName' );
494
495 if ( ! $block_name ) {
496 continue;
497 }
498
499 $block_has_mappings = Arr::get( $mapped_blocks, $block_name, false );
500
501 if ( $block_has_mappings ) {
502 $block = apply_filters( 'kubio/demo-import/apply-block-mapping', $block, $this );
503 }
504
505 $block['innerBlocks'] = $this->applyBlocksMapping( $block['innerBlocks'], $import_id );
506 $blocks[ $index ] = $block;
507 }
508
509 return $blocks;
510 }
511
512 /**
513 * Check if we need to create a new AJAX request, so that server does not timeout.
514 * And fix the import warning for missing post author.
515 *
516 * @param array $data current post data.
517 *
518 * @return array
519 */
520 public function new_ajax_request_maybe( $data = null ) {
521 $time = microtime( true ) - $this->start_time;
522
523 // We should make a new ajax call, if the time is right.
524 // On CLI execute this in one step.
525 if ( $this->options['is_stepped'] && $time > 20 ) {
526 $this->requestNewAjaxCall();
527 }
528
529 // Set importing author to the current user.
530 // Fixes the [WARNING] Could not find the author for ... log warning messages.
531 $current_user_obj = wp_get_current_user();
532 $data['post_author'] = $current_user_obj->user_login;
533
534 return $data;
535 }
536
537 public function requestNewAjaxCall() {
538 if ( defined( 'WP_CLI' ) && WP_CLI ) {
539 return;
540 }
541 $time = microtime( true ) - $this->start_time;
542 $response = array(
543 'status' => 'requires-new-ajax-call',
544 'log' => 'Request time almost expired. Start new request!: ' . $time,
545 'num_of_imported_posts' => count( $this->mapping['post'] ),
546 );
547
548 // Add message to log file.
549 $this->logger->info( __( 'New AJAX call!', 'kubio' ) );
550
551 // Set the current importer state, so it can be continued on the next AJAX call.
552 $this->set_current_importer_data();
553
554 // Send the request for a new AJAX call.
555 wp_send_json( $response );
556 }
557
558 /**
559 * Save current importer data to the DB, for later use.
560 */
561 public function set_current_importer_data() {
562 $data = array(
563 'options' => $this->options,
564 'mapping' => $this->mapping,
565 'requires_remapping' => $this->requires_remapping,
566 'exists' => $this->exists,
567 'user_slug_override' => $this->user_slug_override,
568 'url_remap' => $this->url_remap,
569 'featured_images' => $this->featured_images,
570 );
571
572 $this->save_current_import_data_transient( $data );
573 }
574
575 /**
576 * Set the importer data to the transient.
577 *
578 * @param array $data Data to be saved to the transient.
579 */
580 public function save_current_import_data_transient( $data ) {
581 set_transient( static::WXR_IMPORTER_TRANSIENT, $data, MINUTE_IN_SECONDS );
582 }
583
584 protected function post_process_posts( $todo ) {
585 $this->logger->info( __( 'Posts post processing', 'kubio' ) );
586 foreach ( $todo as $post_id => $_ ) {
587 $terms = get_post_meta( $post_id, '_wxr_import_term', false );
588 foreach ( $terms as $term ) {
589
590 $found_term = term_exists( $term['slug'], $term['taxonomy'] );
591
592 if ( ! $found_term ) {
593 $this->logger->info(
594 sprintf(
595 // translators: %1$s = term slug, %2$s = taxonomy
596 __( 'Create term `%1$s` in `%2$s`', 'kubio' ),
597 $term['slug'],
598 $term['taxonomy']
599 )
600 );
601 $found_term = wp_insert_term(
602 $term['name'],
603 $term['taxonomy'],
604 array(
605 'slug' => $term['slug'],
606 )
607 );
608 }
609
610 if ( is_wp_error( $found_term ) ) {
611 continue;
612 }
613
614 $term_id = (int) $found_term['term_id'];
615 wp_set_post_terms( $post_id, array( $term_id ), $term['taxonomy'], false );
616 }
617
618 delete_post_meta( $post_id, '_wxr_import_term' );
619 }
620
621 parent::post_process_posts( $todo );
622 }
623
624 public function getBaseUrl() {
625 return $this->base_url;
626 }
627 }
628