From c27c62d9ce0d9cebc06dde49295dd1ecea0d4a56 Mon Sep 17 00:00:00 2001 From: Cam Findlay Date: Sun, 1 Nov 2015 10:47:44 +1300 Subject: [PATCH] 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. --- code/compat/pages/BlogEntry.php | 112 -------- code/compat/pages/BlogHolder.php | 73 ----- code/compat/pages/BlogTree.php | 53 ---- code/compat/tasks/BlogMigrationTask.php | 89 ------ code/compat/tasks/MigratableObject.php | 8 - code/compat/widgets/ArchiveWidget.php | 52 ---- code/compat/widgets/TagCloudWidget.php | 44 --- code/tasks/BlogMigrationTask.php | 357 ++++++++++++++++++++++++ 8 files changed, 357 insertions(+), 431 deletions(-) delete mode 100644 code/compat/pages/BlogEntry.php delete mode 100644 code/compat/pages/BlogHolder.php delete mode 100644 code/compat/pages/BlogTree.php delete mode 100644 code/compat/tasks/BlogMigrationTask.php delete mode 100644 code/compat/tasks/MigratableObject.php delete mode 100644 code/compat/widgets/ArchiveWidget.php delete mode 100644 code/compat/widgets/TagCloudWidget.php create mode 100644 code/tasks/BlogMigrationTask.php diff --git a/code/compat/pages/BlogEntry.php b/code/compat/pages/BlogEntry.php deleted file mode 100644 index 7068752..0000000 --- a/code/compat/pages/BlogEntry.php +++ /dev/null @@ -1,112 +0,0 @@ - 'SS_Datetime', - 'Author' => 'Text', - 'Tags' => 'Text', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() { - - //Migrate comma separated tags into BlogTag objects. - foreach($this->TagNames() as $tag) { - - $existingTag = BlogTag::get()->filter(array('Title' => $tag, 'BlogID' => $this->ParentID)); - if($existingTag->count()) { - //if tag already exists we will simply add it to this post. - $tagObject = $existingTag->First(); - - } else { - - //if the tag is now we create it and add it to this post. - $tagObject = new BlogTag(); - $tagObject->Title = $tag; - $tagObject->BlogID = $this->ParentID; - $tagObject->write(); - - - } - - if($tagObject){ - $this->Tags()->add($tagObject); - } - } - - //Store if the original entity was published or not (draft) - $published = $this->IsPublished(); - // If a user has subclassed BlogEntry, it should not be turned into a BlogPost. - if($this->ClassName === 'BlogEntry') { - $this->ClassName = 'BlogPost'; - $this->RecordClassName = 'BlogPost'; - } - //Migrate these key data attributes - $this->PublishDate = $this->Date; - $this->AuthorNames = $this->Author; - $this->InheritSideBar = true; - - //Write and additionally publish the item if it was published before. - $this->write(); - if($published){ - $this->publish('Stage','Live'); - $message = "PUBLISHED: "; - } else { - $message = "DRAFT: "; - } - - return $message . $this->Title; - } - - /** - * Safely split and parse all distinct tags assigned to this BlogEntry. - * - * @deprecated since version 2.0 - * - * @return array - */ - public function TagNames() { - $tags = preg_split('/\s*,\s*/', trim($this->Tags)); - - $results = array(); - - foreach($tags as $tag) { - if($tag) $results[mb_strtolower($tag)] = $tag; - } - - return $results; - } - -} - -/** - * @deprecated since version 2.0 - */ -class BlogEntry_Controller extends BlogPost_Controller { - -} diff --git a/code/compat/pages/BlogHolder.php b/code/compat/pages/BlogHolder.php deleted file mode 100644 index 86b8cdd..0000000 --- a/code/compat/pages/BlogHolder.php +++ /dev/null @@ -1,73 +0,0 @@ - 'Boolean', - 'ShowFullEntry' => 'Boolean', - ); - - /** - * @var array - */ - private static $has_one = array( - 'Owner' => 'Member', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) { - return false; - } - - - //Overload these to stop the Uncaught Exception: Object->__call(): the method 'parent' does not exist on 'BlogHolder' error. - public function validURLSegment() { - return true; - } - public function syncLinkTracking() { - return null; - } - - /** - * {@inheritdoc} - */ - public function up() { - - $published = $this->IsPublished(); - - if($this->ClassName === 'BlogHolder') { - $this->ClassName = 'Blog'; - $this->RecordClassName = 'Blog'; - $this->PostsPerPage = 10; - $this->write(); - } - - if($published){ - $this->publish('Stage','Live'); - $message = "PUBLISHED: "; - } else { - $message = "DRAFT: "; - } - - return $message . $this->Title; - } -} - -/** - * @deprecated since version 2.0 - */ -class BlogHolder_Controller extends BlogTree_Controller { - -} diff --git a/code/compat/pages/BlogTree.php b/code/compat/pages/BlogTree.php deleted file mode 100644 index 9c72681..0000000 --- a/code/compat/pages/BlogTree.php +++ /dev/null @@ -1,53 +0,0 @@ - 'Varchar(255)', - 'LandingPageFreshness' => 'Varchar', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() { - $published = $this->IsPublished(); - if($this->ClassName === 'BlogTree') { - $this->ClassName = 'Page'; - $this->RecordClassName = 'Page'; - $this->write(); - } - if($published){ - $this->publish('Stage','Live'); - $message = "PUBLISHED: "; - } else { - $message = "DRAFT: "; - } - - return $message . $this->Title; - } -} - -/** - * @deprecated since version 2.0 - */ -class BlogTree_Controller extends Page_Controller { - -} diff --git a/code/compat/tasks/BlogMigrationTask.php b/code/compat/tasks/BlogMigrationTask.php deleted file mode 100644 index d222633..0000000 --- a/code/compat/tasks/BlogMigrationTask.php +++ /dev/null @@ -1,89 +0,0 @@ -message('Migrating legacy blog records'); - - foreach($classes as $class) { - - $this->upClass($class); - } - - } - - /** - * @param string $text - */ - protected function message($text) { - if(Controller::curr() instanceof DatabaseAdmin) { - DB::alteration_message($text, 'obsolete'); - } else { - echo $text . "
"; - } - } - - /** - * Migrate records of a single class - * - * @param string $class - * @param null|string $stage - */ - protected function upClass($class) { - if(!class_exists($class)) { - return; - } - - if(is_subclass_of($class, 'SiteTree')) { - $items = SiteTree::get()->filter('ClassName', $class); - } else { - $items = $class::get(); - } - - if($count = $items->count()) { - $this->message( - sprintf( - 'Migrating %s legacy %s records.', - $count, - $class - ) - ); - - foreach($items as $item) { - $cancel = $item->extend('onBeforeUp'); - - if($cancel && min($cancel) === false) { - continue; - } - - /** - * @var MigratableObject $item - */ - $result = $item->up(); - $this->message($result); - - $item->extend('onAfterUp'); - } - } - } - - /** - * {@inheritdoc} - */ - public function down() { - $this->message('BlogMigrationTask::down() not implemented'); - } -} diff --git a/code/compat/tasks/MigratableObject.php b/code/compat/tasks/MigratableObject.php deleted file mode 100644 index 59af4ae..0000000 --- a/code/compat/tasks/MigratableObject.php +++ /dev/null @@ -1,8 +0,0 @@ - 'Varchar', - ); - - /** - * @var array - */ - private static $only_available_in = array( - 'none', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() { - if($this->DisplayMode) { - $this->ArchiveType = 'Monthly'; - - if($this->DisplayMode === 'year') { - $this->ArchiveType = 'Yearly'; - } - } - - $this->ClassName = 'BlogArchiveWidget'; - $this->write(); - return "Migrated " . $this->ArchiveType . " archive widget"; - - } -} diff --git a/code/compat/widgets/TagCloudWidget.php b/code/compat/widgets/TagCloudWidget.php deleted file mode 100644 index 5f7f832..0000000 --- a/code/compat/widgets/TagCloudWidget.php +++ /dev/null @@ -1,44 +0,0 @@ - 'Varchar', - 'Limit' => 'Int', - 'Sortby' => 'Varchar', - ); - - /** - * @var array - */ - private static $only_available_in = array( - 'none', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() { - $this->ClassName = 'BlogTagsWidget'; - $this->write(); - return "Migrated " . $this->Title . " widget"; - } -} diff --git a/code/tasks/BlogMigrationTask.php b/code/tasks/BlogMigrationTask.php new file mode 100644 index 0000000..15dfcb4 --- /dev/null +++ b/code/tasks/BlogMigrationTask.php @@ -0,0 +1,357 @@ +eol = Director::is_cli() ? PHP_EOL : "
"; + + //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 dev/build' . $this->eol; + } + + //THE MIGRATION + if($request->getVar('migration') != 1){ + echo $this->eol . 'Ready to run the migration? ' . $this->eol . $this->eol . + 'Run migration only' . $this->eol . $this->eol . + 'Run migration and clean old blog tables' . $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; + + } +} \ No newline at end of file