├── .editorconfig
├── .gitignore
├── LICENSE.md
├── README.md
├── composer.json
├── config
└── content-migration.php
└── src
├── ClearContent.php
├── Console
└── ContentMigrationCommand.php
├── ContentMigration.php
├── Facades
├── ClearContent.php
└── ContentMigration.php
└── Providers
└── ContentMigrationServiceProvider.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.php]
15 | indent_size = 4
16 |
17 | [*.blade.php]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) istogram - Web Development Studio
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Wordpress API Content Migration
2 |
3 | This Acorn package provides Artisan commands to migrate a WP site's content using the WP REST API.
4 |
5 | ## Installation
6 |
7 | You can install this package with Composer:
8 |
9 | ```bash
10 | composer require istogram/wp-api-content-migration
11 | ```
12 |
13 | You can publish the config file with:
14 |
15 | ```shell
16 | wp acorn vendor:publish --provider="istogram\WpApiContentMigration\Providers\ContentMigrationServiceProvider"
17 | ```
18 |
19 | ## Configuration
20 |
21 | ### Allow SVG media uploads
22 |
23 | If you want to allow SVG media uploads you will need to set the config option:
24 |
25 | ```php
26 | 'allow_svg_media' => true
27 | ```
28 |
29 | ## Usage
30 |
31 | To migrate WP content from a WP site, using the WP REST API, to the local site use this command replacing {domain} with the domain of the Live WP site :
32 |
33 | ```shell
34 | wp acorn migrate:content {domain}
35 | ```
36 |
37 | When no options are applied, the command will proceed step by step, asking for confirmation before each step is applied.
38 |
39 | If you want to clear the current taxonomies, media, posts and pages of the local site you may use this option :
40 |
41 | ```shell
42 | wp acorn migrate:content {domain} --clear-all
43 | ```
44 |
45 | You may also use this option if you want to migrate all WP content without confirmations :
46 |
47 | ```shell
48 | wp acorn migrate:content {domain} --clear-all --migrate-all
49 | ```
50 |
51 | Please be aware that if you choose to clear any of the existing taxonomies, media, posts or pages this will delete entirely all the relevant content from the local site DB. This action is irreversible, so it's safer to have a DB backup first.
52 |
53 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "istogram/wp-api-content-migration",
3 | "type": "package",
4 | "description": "An Acorn package to migrate content from a WP API to a local WP installation",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Timos Zabetakis",
9 | "email": "timoszab@gmail.com"
10 | }
11 | ],
12 | "autoload": {
13 | "psr-4": {
14 | "istogram\\WpApiContentMigration\\": "src/"
15 | }
16 | },
17 | "require": {
18 | "php": "^8.0"
19 | },
20 | "extra": {
21 | "acorn": {
22 | "providers": [
23 | "istogram\\WpApiContentMigration\\Providers\\ContentMigrationServiceProvider"
24 | ],
25 | "aliases": {
26 | "ClearContent": "istogram\\WpApiContentMigration\\Facades\\ClearContent",
27 | "ContentMigration": "istogram\\WpApiContentMigration\\Facades\\ContentMigration"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/config/content-migration.php:
--------------------------------------------------------------------------------
1 | '%current%/%max% [%bar%>] %elapsed%',
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Allow SVG media
27 | |--------------------------------------------------------------------------
28 | |
29 | | Set to true to allow SVG media files to be imported.
30 | */
31 | 'allow_svg_media' => false,
32 | ];
33 |
--------------------------------------------------------------------------------
/src/ClearContent.php:
--------------------------------------------------------------------------------
1 | app = $app;
24 | }
25 |
26 | /**
27 | * Clear WP taxonomies. This will delete all categories and tags.
28 | *
29 | * @return void
30 | */
31 | public function clearTaxonomies()
32 | {
33 | try {
34 | // delete all terms
35 | $terms = get_terms([
36 | 'taxonomy' => 'category',
37 | 'hide_empty' => false,
38 | ]);
39 |
40 | foreach ($terms as $term) {
41 | wp_delete_term($term->term_id, 'category');
42 | }
43 |
44 | $terms = get_terms([
45 | 'taxonomy' => 'post_tag',
46 | 'hide_empty' => false,
47 | ]);
48 |
49 | foreach ($terms as $term) {
50 | wp_delete_term($term->term_id, 'post_tag');
51 | }
52 |
53 | // delete all term metadata
54 | $this->clearImportedMeta('category');
55 | $this->clearImportedMeta('tag');
56 |
57 | // return response
58 | return 'Taxonomies cleared';
59 | } catch (\Exception $e) {
60 | return $e->getMessage();
61 | }
62 | }
63 |
64 | /**
65 | * Clear WP media. This will delete all media files and their metadata.
66 | *
67 | * @return void
68 | */
69 | public function clearMedia()
70 | {
71 | try {
72 | // delete all media
73 | $media = get_posts([
74 | 'post_type' => 'attachment',
75 | 'numberposts' => -1,
76 | 'post_status' => null,
77 | ]);
78 |
79 | foreach ($media as $medium) {
80 | wp_delete_attachment($medium->ID, true);
81 | }
82 |
83 | // delete all media metadata
84 | $this->app->db->table('postmeta')->where('meta_key', '_wp_attached_file')->delete();
85 | $this->app->db->table('postmeta')->where('meta_key', '_wp_attachment_metadata')->delete();
86 | $this->clearImportedMeta('featured_media');
87 |
88 | // delete files in uploads directory
89 | $uploads_dir = wp_upload_dir();
90 |
91 | $files = glob($uploads_dir['basedir'].'/*');
92 |
93 | foreach ($files as $file) {
94 | if (is_file($file)) {
95 | unlink($file);
96 | }
97 | }
98 |
99 | return 'Media cleared';
100 | } catch (\Exception $e) {
101 | return $e->getMessage();
102 | }
103 | }
104 |
105 | /**
106 | * Clear WP posts. This will also clear all post metadata.
107 | *
108 | * @return void
109 | */
110 | public function clearPosts()
111 | {
112 | try {
113 | // delete all posts
114 | $posts = get_posts([
115 | 'post_type' => 'post',
116 | 'numberposts' => -1,
117 | 'post_status' => null,
118 | ]);
119 |
120 | foreach ($posts as $post) {
121 | wp_delete_post($post->ID, true);
122 | }
123 |
124 | // delete all post metadata
125 | $this->clearImportedMeta('post');
126 |
127 | return 'Posts cleared';
128 | } catch (\Exception $e) {
129 | return $e->getMessage();
130 | }
131 | }
132 |
133 | /**
134 | * Clear WP pages. This method also clears all imported page metadata.
135 | *
136 | * @return void
137 | */
138 | public function clearPages()
139 | {
140 | try {
141 | // delete all pages
142 | $pages = get_posts([
143 | 'post_type' => 'page',
144 | 'numberposts' => -1,
145 | 'post_status' => null,
146 | ]);
147 |
148 | foreach ($pages as $page) {
149 | wp_delete_post($page->ID, true);
150 | }
151 |
152 | // delete all page metadata
153 | $this->clearImportedMeta('page');
154 |
155 | return 'Pages cleared';
156 | } catch (\Exception $e) {
157 | return $e->getMessage();
158 | }
159 | }
160 |
161 | /**
162 | * Clear imported meta data. This method is used to clear meta data
163 | * that was imported from WP API.
164 | *
165 | * @return void
166 | */
167 | public function clearImportedMeta($type)
168 | {
169 | switch ($type) {
170 | case 'category':
171 | $this->app->db->table('termmeta')->where('meta_key', 'wp_api_prev_category_id')->delete();
172 | break;
173 | case 'tag':
174 | $this->app->db->table('termmeta')->where('meta_key', 'wp_api_prev_tag_id')->delete();
175 | break;
176 | case 'featured_media':
177 | $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_featured_media_id')->delete();
178 | break;
179 | case 'post':
180 | $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_post_id')->delete();
181 | break;
182 | case 'page':
183 | $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_page_id')->delete();
184 | $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_page_parent_id')->delete();
185 | break;
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/Console/ContentMigrationCommand.php:
--------------------------------------------------------------------------------
1 | argument('domain')) {
34 | $this->argument('domain', $this->ask('What is the domain of the WP site?'));
35 | }
36 |
37 | switch ($this->option('clear-all')) {
38 | case true:
39 | if ($this->confirm('Do you want to clear all content?')) {
40 | $this->info('Clearing all content');
41 | $this->clearTaxonomies();
42 | $this->clearMedia();
43 | $this->clearPosts();
44 | $this->clearPages();
45 | }
46 | break;
47 | case false:
48 | $this->info('Clearing content');
49 | $this->confirmClear('taxonomies') ? $this->clearTaxonomies() : null;
50 | $this->confirmClear('media') ? $this->clearMedia() : null;
51 | $this->confirmClear('posts') ? $this->clearPosts() : null;
52 | $this->confirmClear('pages') ? $this->clearPages() : null;
53 | break;
54 | }
55 |
56 | switch ($this->option('migrate-all')) {
57 | case true:
58 | $this->info('Migrating all content');
59 | $this->migrateCategories();
60 | $this->migrateTags();
61 | $this->migrateMedia();
62 | $this->migratePosts();
63 | $this->migratePages();
64 | $this->clearImportedMeta();
65 | break;
66 | case false:
67 | $this->info('Migrating content');
68 | $this->confirmMigrate('categories') ? $this->migrateCategories() : null;
69 | $this->confirmMigrate('tags') ? $this->migrateTags() : null;
70 | $this->confirmMigrate('media') ? $this->migrateMedia() : null;
71 | $this->confirmMigrate('posts') ? $this->migratePosts() : null;
72 | $this->confirmMigrate('pages') ? $this->migratePages() : null;
73 | break;
74 | }
75 | }
76 |
77 | /**
78 | * Clear taxonomies (categories and tags). This will delete all categories and tags.
79 | *
80 | * @return void
81 | */
82 | public function clearTaxonomies()
83 | {
84 | $response = ClearContent::clearTaxonomies();
85 |
86 | return $this->info($response);
87 | }
88 |
89 | /**
90 | * Clear media (attachments). This will delete all media files and their metadata.
91 | *
92 | * @return void
93 | */
94 | public function clearMedia()
95 | {
96 | $response = ClearContent::clearMedia();
97 |
98 | $this->info($response);
99 | }
100 |
101 | /**
102 | * Clear posts (articles). This will delete all posts and their metadata.
103 | *
104 | * @return void
105 | */
106 | public function clearPosts()
107 | {
108 | $response = ClearContent::clearPosts();
109 |
110 | $this->info($response);
111 | }
112 |
113 | /**
114 | * Clear pages (static pages). This will delete all pages and their metadata.
115 | *
116 | * @return void
117 | */
118 | public function clearPages()
119 | {
120 | $response = ClearContent::clearPages();
121 |
122 | $this->info($response);
123 | }
124 |
125 | /**
126 | * Clear imported meta data.
127 | *
128 | * @return void
129 | */
130 | public function clearImportedMeta()
131 | {
132 | $types = ['category', 'tag', 'featured_media', 'post', 'page'];
133 |
134 | foreach ($types as $type) {
135 | ClearContent::clearImportedMeta($type);
136 | }
137 |
138 | $this->info('Cleared imported meta');
139 | }
140 |
141 | /**
142 | * Confirm clear data.
143 | *
144 | * @param string $type
145 | *
146 | * @return void
147 | */
148 | public function confirmClear($type)
149 | {
150 | // check if user wants to clear data
151 | $confirm = $this->confirm("Are you sure you want to clear all $type?");
152 |
153 | if ($confirm) {
154 | return true;
155 | }
156 | }
157 |
158 | /**
159 | * Confirm migrate data.
160 | *
161 | * @param string $type
162 | *
163 | * @return void
164 | */
165 | public function confirmMigrate($type)
166 | {
167 | // check if user wants to migrate data
168 | $confirm = $this->confirm("Are you sure you want to migrate all $type?");
169 |
170 | if ($confirm) {
171 | return true;
172 | }
173 | }
174 |
175 | /**
176 | * Fetch data from WP API. This method is used to fetch data from WP API.
177 | *
178 | * @param string $endpoint
179 | *
180 | * @return void
181 | */
182 | public function fetchData($endpoint)
183 | {
184 | $response = wp_remote_get($endpoint);
185 |
186 | if (is_wp_error($response)) {
187 | $error_message = $response->get_error_message();
188 | $this->error("Error: $error_message");
189 |
190 | return;
191 | }
192 |
193 | return json_decode(wp_remote_retrieve_body($response));
194 | }
195 |
196 | /**
197 | * Fetch total pages from WP API. This method is used to fetch the total number of pages from WP API.
198 | *
199 | * @param string $endpoint
200 | *
201 | * @return void
202 | */
203 | public function fetchTotalPages($endpoint)
204 | {
205 | $response = wp_remote_get($endpoint);
206 |
207 | if (is_wp_error($response)) {
208 | $error_message = $response->get_error_message();
209 | $this->error("Error: $error_message");
210 |
211 | return;
212 | }
213 |
214 | return wp_remote_retrieve_header($response, 'X-WP-TotalPages');
215 | }
216 |
217 | /**
218 | * Fetch page data from WP API. This method is used to fetch data from a specific page.
219 | *
220 | * @param string $endpoint
221 | * @param int $page
222 | *
223 | * @return void
224 | */
225 | public function fetchPageData($endpoint, $page)
226 | {
227 | $response = wp_remote_get($endpoint.'?page='.$page);
228 |
229 | if (is_wp_error($response)) {
230 | $error_message = $response->get_error_message();
231 | $this->error("Error: $error_message");
232 |
233 | return;
234 | }
235 |
236 | return json_decode(wp_remote_retrieve_body($response));
237 | }
238 |
239 | /**
240 | * Migrate categories. This method is used to migrate categories from WP API.
241 | *
242 | * @return void
243 | */
244 | public function migrateCategories()
245 | {
246 | $this->info('Migrating WP categories');
247 | $this->line('');
248 |
249 | // set categories endpoint
250 | $categories_endpoint = $this->argument('domain').'/wp-json/wp/v2/categories';
251 |
252 | // get categories
253 | $categories = $this->fetchData($categories_endpoint);
254 |
255 | // get total pages
256 | $total_pages = $this->fetchTotalPages($categories_endpoint);
257 |
258 | // create progress bar
259 | $progressBar = $this->output->createProgressBar($total_pages);
260 |
261 | // set progress bar format
262 | $progressBar->setFormat(config('content-migration.progress_bar_format'));
263 |
264 | // loop through all pages
265 | for ($page = 1; $page <= $total_pages; ++$page) {
266 | // get categories
267 | $categories = $this->fetchPageData($categories_endpoint, $page);
268 |
269 | // filter parent categories
270 | $parent_categories = array_filter($categories, function ($category) {
271 | return $category->parent === 0;
272 | });
273 |
274 | // create parent categories
275 | foreach ($parent_categories as $category) {
276 | ContentMigration::createCategory($category);
277 | }
278 |
279 | // filter child categories
280 | $child_categories = array_filter($categories, function ($category) {
281 | return $category->parent !== 0;
282 | });
283 |
284 | // create child categories
285 | foreach ($child_categories as $category) {
286 | ContentMigration::createCategory($category);
287 | }
288 |
289 | // output progress
290 | $progressBar->advance();
291 |
292 | // break if last page
293 | if ($page === $total_pages) {
294 | break;
295 | }
296 | }
297 |
298 | $progressBar->finish();
299 | $this->printFormattedEndMessage('Migrated categories');
300 | }
301 |
302 | /**
303 | * Migrate tags. This method is used to migrate tags from WP API.
304 | *
305 | * @return void
306 | */
307 | public function migrateTags()
308 | {
309 | $this->info('Migrating tags');
310 | $this->line('');
311 |
312 | // get tags
313 | $tags_endpoint = $this->argument('domain').'/wp-json/wp/v2/tags';
314 |
315 | $tags = $this->fetchData($tags_endpoint);
316 |
317 | // get total pages
318 | $total_pages = $this->fetchTotalPages($tags_endpoint);
319 |
320 | // create progress bar
321 | $progressBar = $this->output->createProgressBar($total_pages);
322 |
323 | // set progress bar format
324 | $progressBar->setFormat(config('content-migration.progress_bar_format'));
325 |
326 | // loop through all pages
327 | for ($page = 1; $page <= $total_pages; ++$page) {
328 | $tags = $this->fetchPageData($tags_endpoint, $page);
329 |
330 | // create tags
331 | foreach ($tags as $tag) {
332 | ContentMigration::createTag($tag);
333 | }
334 |
335 | // output progress
336 | $progressBar->advance();
337 |
338 | // break if last page
339 | if ($page === $total_pages) {
340 | break;
341 | }
342 | }
343 |
344 | $progressBar->finish();
345 | $this->printFormattedEndMessage('Migrated tags');
346 | }
347 |
348 | /**
349 | * Migrate media. This method is used to migrate media from WP API.
350 | *
351 | * @return void
352 | */
353 | public function migrateMedia()
354 | {
355 | $this->info('Migrating media');
356 | $this->line('');
357 |
358 | // set media endpoint
359 | $media_endpoint = $this->argument('domain').'/wp-json/wp/v2/media';
360 |
361 | // get media
362 | $media = $this->fetchData($media_endpoint);
363 |
364 | // get total pages
365 | $total_pages = $this->fetchTotalPages($media_endpoint);
366 |
367 | // create progress bar
368 | $progressBar = $this->output->createProgressBar($total_pages);
369 |
370 | // set progress bar format
371 | $progressBar->setFormat(config('content-migration.progress_bar_format'));
372 |
373 | // loop through all pages
374 | for ($page = 1; $page <= $total_pages; ++$page) {
375 | $media = $this->fetchPageData($media_endpoint, $page);
376 |
377 | // create media
378 | foreach ($media as $medium) {
379 | ContentMigration::createMedia($medium);
380 | }
381 |
382 | // output progress
383 | $progressBar->advance();
384 |
385 | // break if last page
386 | if ($page === $total_pages) {
387 | break;
388 | }
389 | }
390 |
391 | $progressBar->finish();
392 | $this->printFormattedEndMessage('Migrated media');
393 | }
394 |
395 | /**
396 | * Migrate posts. This method is used to migrate posts from WP API.
397 | *
398 | * @return void
399 | */
400 | public function migratePosts()
401 | {
402 | $this->info('Migrating posts');
403 | $this->line('');
404 |
405 | // set posts endpoint
406 | $posts_endpoint = $this->argument('domain').'/wp-json/wp/v2/posts';
407 |
408 | // get posts
409 | $posts = $this->fetchData($posts_endpoint);
410 |
411 | // get total pages
412 | $total_pages = $this->fetchTotalPages($posts_endpoint);
413 |
414 | // create progress bar
415 | $progressBar = $this->output->createProgressBar($total_pages);
416 |
417 | // set progress bar format
418 | $progressBar->setFormat(config('content-migration.progress_bar_format'));
419 |
420 | // loop through all pages
421 | for ($page = 1; $page <= $total_pages; ++$page) {
422 | $posts = $this->fetchPageData($posts_endpoint, $page);
423 |
424 | // create posts
425 | foreach ($posts as $post) {
426 | ContentMigration::createPost($post);
427 | }
428 |
429 | // output progress
430 | $progressBar->advance();
431 |
432 | // break if last page
433 | if ($page === $total_pages) {
434 | break;
435 | }
436 | }
437 |
438 | $progressBar->finish();
439 | $this->printFormattedEndMessage('Migrated posts');
440 | }
441 |
442 | /**
443 | * Migrate pages. This method is used to migrate pages from WP API.
444 | *
445 | * @return void
446 | */
447 | public function migratePages()
448 | {
449 | $this->info('Migrating pages');
450 | $this->line('');
451 |
452 | // set endpoint
453 | $pages_endpoint = $this->argument('domain').'/wp-json/wp/v2/pages';
454 |
455 | // get pages
456 | $pages = $this->fetchData($pages_endpoint);
457 |
458 | // get total pages
459 | $total_pages = $this->fetchTotalPages($pages_endpoint);
460 |
461 | // create progress bar
462 | $progressBar = $this->output->createProgressBar($total_pages);
463 |
464 | // set progress bar format
465 | $progressBar->setFormat(config('content-migration.progress_bar_format'));
466 |
467 | // loop through all pages
468 | for ($page = 1; $page <= $total_pages; ++$page) {
469 | $pages = $this->fetchPageData($pages_endpoint, $page);
470 |
471 | // filter parent pages
472 | $parent_pages = array_filter($pages, function ($page) {
473 | return $page->parent === 0;
474 | });
475 |
476 | // create parent pages
477 | foreach ($parent_pages as $pageToMigrate) {
478 | ContentMigration::createPage($pageToMigrate);
479 | }
480 |
481 | // filter child pages
482 | $child_pages = array_filter($pages, function ($page) {
483 | return $page->parent !== 0;
484 | });
485 |
486 | // create child pages
487 | foreach ($child_pages as $pageToMigrate) {
488 | ContentMigration::createPage($pageToMigrate);
489 | }
490 |
491 | // output progress
492 | $progressBar->advance();
493 |
494 | // break if last page
495 | if ($page === $total_pages) {
496 | break;
497 | }
498 | }
499 |
500 | $progressBar->finish();
501 | $this->printFormattedEndMessage('Migrated pages');
502 | }
503 |
504 | /**
505 | * Print formatted end message.
506 | *
507 | * @param string $message
508 | *
509 | * @return void
510 | */
511 | public function printFormattedEndMessage($message)
512 | {
513 | $this->line('');
514 | $this->line('');
515 | $this->info($message);
516 | $this->line('');
517 | }
518 | }
519 |
--------------------------------------------------------------------------------
/src/ContentMigration.php:
--------------------------------------------------------------------------------
1 | app = $app;
24 |
25 | if ($this->app['config']->get('content-migration.allow_svg_media')) {
26 | add_filter( 'upload_mimes', function ( $mimes ){
27 | $mimes['svg'] = 'image/svg+xml';
28 | return $mimes;
29 | });
30 | }
31 | }
32 |
33 | /**
34 | * Create WP category. This method also sets the parent category if it exists.
35 | *
36 | * @param object $category
37 | *
38 | * @return void
39 | */
40 | public function createCategory($category)
41 | {
42 | $params = [
43 | 'slug' => $category->slug,
44 | ];
45 |
46 | if ($category->parent !== 0) {
47 | $parent_category = $this->app->db->table('termmeta')->where('meta_key', 'wp_api_prev_category_id')->where('meta_value', $category->parent)->value('term_id');
48 | $params['parent'] = $parent_category;
49 | }
50 |
51 | try {
52 | // check if category exists
53 | $category_exists = get_term_by('slug', $category->slug, 'category');
54 |
55 | if (empty($category_exists)) {
56 | // create WP term using name and slug and parent
57 | $term = wp_insert_term($category->name, 'category', $params);
58 |
59 | // save term meta for category
60 | update_term_meta($term['term_id'], 'wp_api_prev_category_id', $category->id);
61 | }
62 | } catch (\Exception $e) {
63 | $this->app->log->info('Error creating WP category : '.$e->getMessage());
64 | }
65 | }
66 |
67 | /**
68 | * Create WP tag. This method also sets the slug for the tag.
69 | *
70 | * @param object $tag
71 | *
72 | * @return void
73 | */
74 | public function createTag($tag)
75 | {
76 | try {
77 | // create WP term using name and slug
78 | $term_id = wp_insert_term($tag->name, 'post_tag', [
79 | 'slug' => $tag->slug,
80 | ]);
81 |
82 | // save term meta for tag
83 | update_term_meta($term_id['term_id'], 'wp_api_prev_tag_id', $tag->id);
84 | } catch (\Exception $e) {
85 | $this->app->log->info('Error creating WP tag : '.$e->getMessage());
86 | }
87 | }
88 |
89 | /**
90 | * Create WP media. This method also sets the alt text for the media.
91 | *
92 | * @param object $media
93 | *
94 | * @return void
95 | */
96 | public function createMedia($media)
97 | {
98 | // set params
99 | $params = [
100 | 'file' => $media->source_url,
101 | ];
102 |
103 | try {
104 | // check if media exists
105 | $media_exists = get_posts([
106 | 'post_type' => 'attachment',
107 | 'meta_key' => 'source_url',
108 | 'meta_value' => $media->source_url,
109 | 'numberposts' => 1,
110 | ]);
111 |
112 | if (!empty($media_exists)) {
113 | $this->app->log->info('Media already exists : '.$media->source_url);
114 | return;
115 | }
116 |
117 | // download to temp dir
118 | $temp_file = download_url($params['file']);
119 |
120 | if (is_wp_error($temp_file)) {
121 | $this->app->log->info('Error downloading WP media : '.$temp_file->get_error_message());
122 | return false;
123 | }
124 |
125 | // move the temp file into the uploads directory
126 | $file = [
127 | 'name' => basename($params['file']),
128 | 'type' => mime_content_type($temp_file),
129 | 'tmp_name' => $temp_file,
130 | 'size' => filesize($temp_file),
131 | ];
132 |
133 | $upload = wp_handle_sideload(
134 | $file,
135 | [
136 | 'test_form' => false, // no needs to check 'action' parameter
137 | ]
138 | );
139 |
140 | if (!empty($sideload['error'])) {
141 | return false;
142 | }
143 |
144 | $caption = !empty($media->caption->rendered) ? $media->caption->rendered : $media->title->rendered;
145 | $description = !empty($media->description->rendered) ? $media->description->rendered : $media->caption->rendered;
146 |
147 | // create attachment
148 | $attachment = [
149 | 'post_title' => $media->title->rendered,
150 | 'post_excerpt' => sanitize_text_field($caption),
151 | 'post_content' => sanitize_text_field($description),
152 | 'post_status' => 'inherit',
153 | 'post_mime_type' => $media->mime_type,
154 | ];
155 |
156 | $attach_id = wp_insert_attachment($attachment, $upload['file']);
157 |
158 | // set attachment metadata
159 | wp_update_attachment_metadata($attach_id, wp_generate_attachment_metadata($attach_id, $upload['file']));
160 |
161 | // save media meta
162 | update_post_meta($attach_id, 'wp_api_prev_featured_media_id', $media->id);
163 |
164 | // update alt text
165 | update_post_meta($attach_id, '_wp_attachment_image_alt', sanitize_text_field($media->alt_text ?? $media->caption->rendered));
166 | } catch (\Exception $e) {
167 | $this->app->log->info('Error creating WP media : '.$e->getMessage());
168 | }
169 | }
170 |
171 | /**
172 | * Create WP post. This method also sets the featured image, categories and tags.
173 | *
174 | * @param object $post
175 | *
176 | * @return void
177 | */
178 | public function createPost($post)
179 | {
180 | // set post content
181 | $content = $post->content->rendered;
182 |
183 | // find previous image urls and replace with new urls
184 | $content = preg_replace_callback('/
]+src="([^">]+)"/', function ($matches) use ($post) {
185 | // get attachment_id for meta_key 'wp_api_prev_featured_media_id'
186 | $media_id = $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_featured_media_id')->where('meta_value', $post->featured_media)->value('post_id');
187 |
188 | if (!empty($media_id)) {
189 | $media_url = wp_get_attachment_url($media_id);
190 |
191 | return str_replace($matches[1], $media_url, $matches[0]);
192 | }
193 |
194 | return $matches[0];
195 | }, $content);
196 |
197 | // find links around images and remove
198 | $content = preg_replace('/]+>(
]+>)<\/a>/', '$1', $content);
199 |
200 | // set post excerpt
201 | $excerpt = $post->excerpt->rendered;
202 |
203 | // strip html tags from excerpt
204 | $excerpt = strip_tags($excerpt);
205 |
206 | // set post status
207 | $status = $post->status;
208 |
209 | // set post type
210 | $type = $post->type;
211 |
212 | // set post title
213 | $title = $post->title->rendered;
214 |
215 | // set post slug
216 | $slug = $post->slug;
217 |
218 | // set post author
219 | $author = $post->author;
220 |
221 | // set post created date
222 | $created = $post->date;
223 |
224 | // set post categories from saved meta
225 | $categories = [];
226 |
227 | foreach ($post->categories as $category) {
228 | // get term_id for meta_key 'wp_api_prev_category_id'
229 | $categories[] = $this->app->db->table('termmeta')->where('meta_key', 'wp_api_prev_category_id')->where('meta_value', $category)->value('term_id');
230 | }
231 |
232 | // set post tags from saved meta
233 | $tags = [];
234 |
235 | foreach ($post->tags as $tag) {
236 | // get term_id for meta_key 'wp_api_prev_tag_id'
237 | $tag_id = $this->app->db->table('termmeta')->where('meta_key', 'wp_api_prev_tag_id')->where('meta_value', $tag)->value('term_id');
238 |
239 | // check if tag exists
240 | if (!empty($tag_id)) {
241 | $tags[] = get_term($tag_id)->name;
242 | }
243 | }
244 |
245 | // get post_id for meta_key 'wp_api_prev_featured_media_id'
246 | $media = $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_featured_media_id')->where('meta_value', $post->featured_media)->value('post_id');
247 |
248 | // set post meta
249 | $meta = $post->meta;
250 |
251 | try {
252 | // create WP post
253 | $post_id = wp_insert_post([
254 | 'post_content' => $content,
255 | 'post_excerpt' => $excerpt,
256 | 'post_status' => $status,
257 | 'post_type' => $type,
258 | 'post_title' => $title,
259 | 'post_name' => $slug,
260 | 'post_author' => $author,
261 | 'post_category' => $categories,
262 | 'tags_input' => $tags,
263 | 'meta_input' => $meta,
264 | 'post_date' => $created,
265 | ]);
266 |
267 | // check if post has media
268 | if (!empty($media)) {
269 | // add featured image to post
270 | set_post_thumbnail($post_id, $media);
271 | }
272 | } catch (\Exception $e) {
273 | $this->app->log->info('Error creating WP post : '.$e->getMessage());
274 | }
275 | }
276 |
277 | /**
278 | * Create WP page. This method also sets the parent page if it exists.
279 | *
280 | * @param object $page
281 | *
282 | * @return void
283 | */
284 | public function createPage($page)
285 | {
286 | // set page content
287 | $content = $page->content->rendered;
288 |
289 | // process content to remove anything that includes []
290 | $content = preg_replace('/\[[^\]]+\]/', '', $content);
291 |
292 | // set page excerpt
293 | $excerpt = $page->excerpt->rendered;
294 |
295 | // set page status
296 | $status = $page->status;
297 |
298 | // set page title
299 | $title = $page->title->rendered;
300 |
301 | // set page author
302 | $author = $page->author;
303 |
304 | // set page meta
305 | $meta = $page->meta;
306 |
307 | // set parent page from saved meta
308 | $parentId = $this->app->db->table('postmeta')->where('meta_key', 'wp_api_prev_page_id')->where('meta_value', $page->parent)->value('post_id');
309 |
310 | try {
311 | // create WP page
312 | $page_id = wp_insert_post([
313 | 'post_content' => $content,
314 | 'post_excerpt' => $excerpt,
315 | 'post_status' => $status,
316 | 'post_type' => 'page',
317 | 'post_title' => $title,
318 | 'post_author' => $author,
319 | 'meta_input' => $meta,
320 | 'post_parent' => $parentId,
321 | ]);
322 |
323 | // save parent page to meta
324 | if (!empty($page->parent)) {
325 | update_post_meta($page_id, 'wp_api_prev_page_parent_id', $page->parent);
326 | }
327 |
328 | update_post_meta($page_id, 'wp_api_prev_page_id', $page->id);
329 | } catch (\Exception $e) {
330 | $this->app->log->info('Error creating WP page : '.$e->getMessage());
331 | }
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/src/Facades/ClearContent.php:
--------------------------------------------------------------------------------
1 | app->singleton('ContentMigration', function () {
20 | return new ContentMigration($this->app);
21 | });
22 |
23 | $this->app->singleton('ClearContent', function () {
24 | return new ClearContent($this->app);
25 | });
26 |
27 | $this->mergeConfigFrom(
28 | __DIR__.'/../../config/content-migration.php',
29 | 'content-migration'
30 | );
31 | }
32 |
33 | /**
34 | * Bootstrap any application services.
35 | *
36 | * @return void
37 | */
38 | public function boot()
39 | {
40 | $this->publishes([
41 | __DIR__.'/../../config/content-migration.php' => $this->app->configPath('content-migration.php'),
42 | ], 'config');
43 |
44 | $this->commands([
45 | ContentMigrationCommand::class,
46 | ]);
47 |
48 | $this->app->make('ContentMigration');
49 |
50 | $this->app->make('ClearContent');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------