silverstripe-blog/code/tasks/BlogMigrationTask.php
Cam Findlay c27c62d9ce ENHANCEMENT Refactor of blog migration task to be more reliable.
Previous version of the migration task often cased site breakages. This goes back to basics, moving the data with SQL calls and limited ORM use to ease the data is not modified when mapped to the new blog 2.0 data structure.
2015-11-05 16:24:33 +13:00

357 lines
12 KiB
PHP

<?php
/*
* BlogMigrationTask
*
* Migrates Blog 1.0 to the 2.0 data structure and maps existing content.
*
* Includes extension points as it is a common pattern to have subclassed BlogEntry, BlogHolder etc
* and customised the data model. You can add an Extension to BlogMigrationTask to run custom
* migration before or after the main migration or after the cleanup operation.
*
* @package silverstripe
* @subpackage blog
*
* @method none run() Runs the migration, must pass ?migration=1 to trigger.
* @method int migrateTags() Iterates through all comma-separated tags from blog 1.0 and creates BlogTag objects.
* @method array tagNames() Splits passed in comma separated string of tags
* @method none migrateWidgets() Migrates legacy Widgets if Widget module is installed.
* @method boolean cleanUp() Clears out blog 1.0 obsolete tables if you pass ?cleanup=1 to the task.
*/
class BlogMigrationTask extends BuildTask
{
/**
* @var string $title Shown in the overview on the {@link TaskRunner}
* HTML or CLI interface. Should be short and concise, no HTML allowed.
*/
protected $title = 'Blog 2.0 migration';
/**
* @var string $description Describe the implications the task has,
* and the changes it makes. Accepts HTML formatting.
*/
protected $description = 'Migrates blog 1.0 to 2.0 data structure.';
/*
* Run the migrate
*
* @param object $request
*/
public function run($request) {
// end of line output depending on CLI or browser run.
$this->eol = Director::is_cli() ? PHP_EOL : "<br>";
//PRE-FLIGHT CHECK
// Ensure a dev build has been run by check for some expected tables.
try {
DB::query('SELECT "ID" FROM "Blog"');
DB::query('SELECT "ID" FROM "BlogPost"');
echo "Blog and BlogPost tables exist, you are good to migrate" . $this->eol;
} catch (Exception $e) {
echo 'Ensure you have run a <a href="/dev/build" target="_blank">dev/build</a>' . $this->eol;
}
//THE MIGRATION
if($request->getVar('migration') != 1){
echo $this->eol . 'Ready to run the migration? ' . $this->eol . $this->eol .
'<a href="/dev/tasks/'.__CLASS__.'/?migration=1">Run migration only</a>' . $this->eol . $this->eol .
'<a href="/dev/tasks/'.__CLASS__.'/?migration=1&cleanup=1">Run migration and clean old blog tables</a>' . $this->eol;
exit;
}
$this->extend('onBeforeBlogMigration',$request, $this->eol);
//BlogPost Migration
//Migrate BlogEntry to BlogPost include _Live and _versions
try {
DB::query('
INSERT INTO "BlogPost" ("ID", "PublishDate", "AuthorNames")
(
SELECT "ID", "Date", "Author"
FROM "BlogEntry"
)
');
echo "Migrated BlogEntry to BlogPost" . $this->eol;
} catch (Exception $e) {
echo "BlogEntry to BlogPost migration already run, moving along..." . $this->eol;
}
//BlogPost_Live
try {
DB::query('
INSERT INTO "BlogPost_Live" ("ID", "PublishDate", "AuthorNames")
(
SELECT "ID", "Date", "Author"
FROM "BlogEntry_Live"
)
');
echo "Migrated BlogEntry_Live to BlogPost_Live" . $this->eol;
} catch (Exception $e) {
echo "BlogEntry_Live to BlogPost_Live migration already run, moving along..." . $this->eol;
}
//BlogPost_version
try {
DB::query('
INSERT INTO "BlogPost_versions" ("ID", "RecordID", "Version", "PublishDate", "AuthorNames")
(
SELECT "ID", "RecordID", "Version", "Date", "Author"
FROM "BlogEntry_versions"
)
');
echo "Migrated BlogEntry_versions to BlogPost_versions" . $this->eol;
} catch (Exception $e) {
echo "BlogEntry_versions to BlogPost_versions migration already run, moving along..." . $this->eol;
}
//SiteTree ClassName BlogEntry to BlogPost
try {
DB::query('UPDATE "SiteTree" SET "ClassName" = \'BlogPost\' WHERE "ClassName" = \'BlogEntry\'');
DB::query('UPDATE "SiteTree_Live" SET "ClassName" = \'BlogPost\' WHERE "ClassName" = \'BlogEntry\'');
DB::query('UPDATE "SiteTree_versions" SET "ClassName" = \'BlogPost\' WHERE "ClassName" = \'BlogEntry\'');
echo "Updated ClassName reference to BlogPost" . $this->eol;
} catch (Exception $e) {
echo $e;
echo "SiteTree BlogPost ClassName migration already run, moving along..." . $this->eol;
}
//Migrate BlogHolder to Blog
//Migrate BlogHolder to Blog include _Live and _versions
try {
DB::query('
INSERT INTO "Blog" ("ID", "PostsPerPage")
(
SELECT "BlogHolder"."ID", 10
FROM "BlogHolder"
)
');
echo "Migrated BlogHolder to Blog" . $this->eol;
} catch (Exception $e) {
echo "BlogHolder to Blog migration already run, moving along..." . $this->eol;
}
//Blog_Live
try {
DB::query('
INSERT INTO "Blog_Live" ("ID", "PostsPerPage")
(
SELECT "BlogHolder_Live"."ID", 10
FROM "BlogHolder_Live"
)
');
echo "Migrated_Live BlogHolder to Blog_Live" . $this->eol;
} catch (Exception $e) {
echo "BlogHolder to Blog migration already run, moving along..." . $this->eol;
}
//Blog_version
try {
DB::query('
INSERT INTO "Blog_versions" ("ID", "RecordID", "Version", "PostsPerPage")
(
SELECT "BlogHolder_versions"."ID", "BlogHolder_versions"."RecordID", "BlogHolder_versions"."Version", 10
FROM "BlogHolder_versions"
)
');
echo "Migrated BlogHolder versions to Blog_versions" . $this->eol;
} catch (Exception $e) {
echo "BlogHolder to Blog migration already run, moving along..." . $this->eol;
}
//SiteTree ClassName BlogEntry to BlogPost
try {
DB::query('UPDATE "SiteTree" SET "ClassName" = \'Blog\' WHERE "ClassName" = \'BlogHolder\'');
DB::query('UPDATE "SiteTree_Live" SET "ClassName" = \'Blog\' WHERE "ClassName" = \'BlogHolder\'');
DB::query('UPDATE "SiteTree_versions" SET "ClassName" = \'Blog\' WHERE "ClassName" = \'BlogHolder\'');
echo "Updated ClassName reference to Blog" . $this->eol;
} catch (Exception $e) {
echo $e;
}
//Migrate BlogTree to Page
try {
DB::query('UPDATE "SiteTree" SET "ClassName" = \'Page\' WHERE "ClassName" = \'SiteTree\'');
DB::query('UPDATE "SiteTree_Live" SET "ClassName" = \'Page\' WHERE "ClassName" = \'SiteTree\'');
DB::query('UPDATE "SiteTree_versions" SET "ClassName" = \'Page\' WHERE "ClassName" = \'SiteTree\'');
echo "Migrated BlogTree to Page" . $this->eol;
} catch (Exception $e) {
echo $e;
}
//Tags migration
try {
$tagcount = $this->migrateTags();
echo "Migrated " . $tagcount . " tags" . $this->eol;
} catch (Exception $e) {
echo "Error in tag migration (may have already run), moving along..." . $this->eol;
}
//Legacy Widget migration
if(class_exists('Widget')) {
try {
$this->migrateWidgets();
} catch (Exception $e) {
echo "Error migrating legacy widgets" . $this->eol;
}
}
$this->extend('onAfterBlogMigration', $request, $this->eol);
//IT'S CLEANUP TIME
if($request->getVar('cleanup') == 1){
try {
$this->cleanUp();
echo "Cleaned up all old blog tables" . $this->eol;
} catch (Exception $e) {
echo "Error with blog table cleanup (may have already cleaned up), moving along..." . $this->eol;
}
}
echo "Migration complete." . $this->eol;
exit;
}
/*
* Migrate the tags
*
* @return int number of migrated tags
*/
protected function migrateTags(){
//1. Get all BlogEntry ID and comma separated tags into an array
$blogtags = DB::query('SELECT ID, Tags FROM BlogEntry_Live')->map('ID','Tags');
$tagcount = 0;
//2. Foreach split the tags into own array
foreach($blogtags as $blogpostid => $tags){
foreach($this->tagNames($tags) as $tag) {
//3a. If it's an existing tag, connect the BlogPost and BlogTag as many_many
$existingTagID = DB::query('SELECT ID FROM BlogTag WHERE Title = \'' .$tag. '\'')->value();
if($existingTagID) {
$tagID = $existingTagID;
} else {
//3b. If it's a new tag, add the BlogTag and then connect the BlogPost and BlogTag as many_many
//Get the ParentID of the BlogPost
$parentID = DB::query('
SELECT "ParentID"
FROM "BlogPost_Live"
LEFT JOIN "SiteTree_Live" ON "SiteTree_Live"."ID" = "BlogPost_Live"."ID"
WHERE "BlogPost_Live"."ID" = \'' . $blogpostid . '\'')->value();
//Write the new tag using ORM to ensure the URLSegment is generated correctly.
$tagObject = BlogTag::create();
$tagObject->Title = $tag;
$tagObject->BlogID = $parentID;
$tagObject->write();
$tagID = $tagObject->ID;
$tagcount++;
}
// 4. Add the tag to the blogpost
DB::query('
INSERT INTO "BlogPost_Tags"
SET
"BlogPostID" = \'' . $blogpostid . '\',
"BlogTagID" = \'' . $tagID . '\''
);
}
}
return $tagcount;
}
/**
* Safely split and parse all distinct tags assigned to a BlogEntry.
*
* @param string comma-separated tags
* @return array
*/
protected function tagNames($tags) {
$tags = preg_split('/\s*,\s*/', trim($tags));
$results = array();
foreach($tags as $tag) {
if($tag) $results[mb_strtolower($tag)] = $tag;
}
return $results;
}
/*
* Migrate ArchiveWidget
*
* We set the BlogID to the first Live BlogID that can be found as we cannot know this value from
* the old blog data structure.
*
* Set some default values for the NumberToDisplay as this was also not part of Blog 1.0 data.
*/
protected function migrateWidgets() {
//Get the first Blog ID else set to 0.
$widgetblogid = DB::query('SELECT "ID" FROM "Blog_Live" ORDER BY "ID" LIMIT 1')->value();
if (!$widgetblogid) {
$widgetblogid = 0;
}
//ArchiveWidget to BlogArchiveWidget
//Yearly
DB::query('
INSERT INTO "BlogArchiveWidget" ("ID", "NumberToDispay", "ArchiveType", "BlogID")
(
SELECT "ID", 10, \'Yearly\', ' . $widgetblogid . '
FROM "ArchiveWidget"
WHERE "DisplayMode" = \'year\'
)
');
//Monthly
DB::query('
INSERT INTO "BlogArchiveWidget" ("ID", "NumberToDispay", "ArchiveType", "BlogID")
(
SELECT "ID", 10, \'Monthly\', ' . $widgetblogid . '
FROM "ArchiveWidget"
WHERE "DisplayMode" = \'month\'
)
');
//Update the Widget ClassName
DB::query('UPDATE "Widget" SET "ClassName" = \'BlogArchiveWidget\' WHERE "ClassName" = \'ArchiveWidget\'');
echo "Migrated ArchiveWidget to BlogArchiveWidget" . $this->eol;
// TagCloudWidget to BlogTagsWidget - sort set to Title ASC as no equivalent of 'frequency' exists in blog 2.0
DB::query('
INSERT INTO "BlogTagsWidget" ("ID", "Limit", "Order", "Direction", "BlogID")
(
SELECT "ID", "Limit", \'Title\', \'ASC\', ' . $widgetblogid . '
FROM "TagCloudWidget"
)
');
//Update the Widget ClassName
DB::query('UPDATE "Widget" SET "ClassName" = \'BlogTagsWidget\' WHERE "ClassName" = \'TagCloudWidget\'');
echo "Migrated TagCloudWidget to BlogTagsWidget" . $this->eol;
}
/*
* Clean up old tables
*
* @return boolean true of the clean up ran without an issue.
*/
protected function cleanUp() {
DB::query('DROP TABLE "BlogEntry"');
DB::query('DROP TABLE "BlogEntry_Live"');
DB::query('DROP TABLE "BlogEntry_versions"');
DB::query('DROP TABLE "BlogHolder"');
DB::query('DROP TABLE "BlogHolder_Live"');
DB::query('DROP TABLE "BlogHolder_versions"');
DB::query('DROP TABLE "BlogTree"');
DB::query('DROP TABLE "BlogTree_Live"');
DB::query('DROP TABLE "BlogTree_versions"');
DB::query('DROP TABLE "ArchiveWidget"');
DB::query('DROP TABLE "TagCloudWidget"');
$this->extend('updateTablesToCleanUp');
return true;
}
}