Last active
February 1, 2016 14:07
-
-
Save joshangell/26d6413a1eb243d4e8a1 to your computer and use it in GitHub Desktop.
Content migration in Craft example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Craft; | |
class MyPlugin_MigrateManagerTask extends BaseTask | |
{ | |
private $_elements; | |
/** | |
* @inheritDoc ITask::getDescription() | |
* | |
* @return string | |
*/ | |
public function getDescription() | |
{ | |
return Craft::t('Migrating old content'); | |
} | |
/** | |
* @inheritDoc ITask::getTotalSteps() | |
* | |
* @return int | |
*/ | |
public function getTotalSteps() | |
{ | |
// Setup the criteria for finding the elements we want to migrate | |
$criteria = craft()->elements->getCriteria(ElementType::Entry); | |
$criteria->enabled = null; | |
$criteria->limit = null; | |
$criteria->status = null; | |
$criteria->sectionId = 15; // The ID of the section we are copying from | |
$elements = $criteria->find(); | |
// Chunk the elements into groups of 10 - if the content is quite | |
// light you may want to up this to 100 or so | |
$this->_elements = array_chunk($elements, 10); | |
return count($this->_elements); | |
} | |
/** | |
* @inheritDoc ITask::runStep() | |
* | |
* @param int $step | |
* | |
* @return bool | |
*/ | |
public function runStep($step) | |
{ | |
// I frequently found I ran out of memory doing these sort of operations | |
// so just bumped up what Craft is allowed to use here - in this to 2.5GB | |
craft()->config->set('phpMaxMemoryLimit', '2560M'); | |
craft()->config->maxPowerCaptain(); | |
// Run the migration as a sub Task with the current chunk of elements | |
return $this->runSubTask('MyPlugin_Migrate', null, array( | |
'elements' => $this->_elements[$step] | |
)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Craft; | |
class MyPlugin_MigrateTask extends BaseTask | |
{ | |
/** | |
* @inheritDoc ITask::getDescription() | |
* | |
* @return string | |
*/ | |
public function getDescription() | |
{ | |
return Craft::t('Migrating ...'); | |
} | |
/** | |
* @inheritDoc ITask::getTotalSteps() | |
* | |
* @return int | |
*/ | |
public function getTotalSteps() | |
{ | |
return count($this->getSettings()->elements); | |
} | |
/** | |
* @inheritDoc ITask::runStep() | |
* | |
* @param int $step | |
* | |
* @return bool | |
*/ | |
public function runStep($step) | |
{ | |
// Again, bump the memory | |
craft()->config->set('phpMaxMemoryLimit', '2560M'); | |
craft()->config->maxPowerCaptain(); | |
// Get the element we want to copy from | |
$element = $this->getSettings()->elements[$step]; | |
// See if the one we are copying to already exists. | |
// What you use to determine this will vary, in this case I just | |
// used the title but you may need something more bullet proof. | |
$criteria = craft()->elements->getCriteria(ElementType::Entry); | |
$criteria->enabled = null; | |
$criteria->limit = null; | |
$criteria->status = null; | |
$criteria->sectionId = 22; // The ID of the section we are copying to | |
$criteria->title = $element->getContent()->title; | |
$targetElement = $criteria->first(); | |
// If we didn’t get an existing element, make one here | |
if (!$targetElement) { | |
$targetElement = new EntryModel(); | |
$targetElement->sectionId = 22; | |
$targetElement->typeId = 23; // The ID of the Entry Type we want | |
} | |
// NOTE: This is where it gets fun - copy your field content! | |
// Copy the blocks from a Matrix field - be aware that if the target | |
// element already exists and has blocks they will be lost. | |
// ref: https://craftcms.stackexchange.com/questions/8517/duplicating-matrix-fields-with-content-from-another-locale | |
$newBlocks = array(); | |
$i = 0; | |
foreach ($element->myMatrixField->find() as $block) | |
{ | |
// Setup a new block | |
$newBlock = new MatrixBlockModel(); | |
$newBlock->fieldId = 4; // Whatever the ID of `myMatrixField` is | |
$newBlock->typeId = $block->getType()->id; | |
$newBlock->ownerId = $targetElement->id; | |
$newBlock->locale = $block->locale; | |
$newBlockContent = $newBlock->getContent(); | |
$values = array(); | |
// Loop the fields on this block | |
foreach ($block->getFieldLayout()->getFields() as $blockFieldLayoutField) | |
{ | |
$field = $blockFieldLayoutField->getField(); | |
$fieldHandle = $field->handle; | |
// Cope with element fields by getting an array of their IDs | |
if (in_array($field->type, array('Assets', 'Entries', 'Categories', 'Tags'))) { | |
$value = $block->$fieldHandle->ids(); | |
} else { | |
// For ‘normal’ fields just copy their content directly | |
$value = $block->$fieldHandle; | |
} | |
$values[$fieldHandle] = $value; | |
} | |
// Set the content on the new block | |
$newBlock->setContentFromPost($values); | |
$newBlocks['new'.$i] = $newBlock; | |
$i++; | |
} | |
// Set the content on the target element | |
$targetElement->setContent(array( | |
// Don’t forget a title! | |
'title' => $element->getContent()->title, | |
// Here are the Matrix blocks we just made | |
'myMatrixField' => $newBlocks, | |
// Same as inside the Matrix, just get the IDs of relationship fields | |
'someAssetField' => $element->someAssetField->ids(), | |
// Simpler fields can just be directly copied | |
'someSimpleTextField' => $element->someSimpleTextField, | |
)); | |
// Keep a bunch of attributes | |
$targetElement->setAttributes(array( | |
'slug' => $sourceElement->slug, | |
'postDate' => $sourceElement->postDate, | |
'expiryDate' => $sourceElement->expiryDate, | |
'enabled' => $sourceElement->enabled, | |
'archived' => $sourceElement->archived, | |
'localeEnabled' => $sourceElement->localeEnabled, | |
)); | |
// Wrap in a transaction in case something goes wrong | |
$transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null; | |
try { | |
// Try and save, throw an exception if it didn’t for some reason | |
if (!craft()->entries->saveEntry($targetElement)) { | |
// Try and get the errors so the log is more useful | |
if ($targetElement->hasErrors()) { | |
$firstError = array_shift($targetElement->getErrors())[0]; | |
throw new Exception(Craft::t('Couldn’t migrate from {title}. First error to correct: {error}', array('title' => $element->title, 'error' => firstError))); | |
} else { | |
throw new Exception(Craft::t('Couldn’t migrate from {title}.', array('title' => $element->title))); | |
} | |
} | |
if ($transaction !== null) | |
{ | |
$transaction->commit(); | |
} | |
} catch (Exception $e) { | |
if ($transaction !== null) | |
{ | |
$transaction->rollback(); | |
} | |
// Log that exception message so we can debug it in the log viewer | |
MyPlugin::log($e->getMessage(), LogLevel::Error); | |
return $e->getMessage(); | |
} | |
return true; | |
} | |
/** | |
* @inheritDoc BaseSavableComponentType::defineSettings() | |
* | |
* @return array | |
*/ | |
protected function defineSettings() | |
{ | |
return array( | |
'elements' => AttributeType::Mixed | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Craft; | |
class MyPluginController extends BaseController | |
{ | |
// This lets anyone run the controller actions we specify, useful for CRON etc | |
protected $allowAnonymous = array('actionMigrate'); | |
/** | |
* Start the migration Task | |
*/ | |
public function actionMigrate() | |
{ | |
// Create the Task | |
craft()->tasks->createTask('MyPlugin_MigrateManager'); | |
if (!craft()->tasks->isTaskRunning()) { | |
// Is there a pending task? | |
$task = craft()->tasks->getNextPendingTask(); | |
if ($task) { | |
// Attempt to close the connection if this is an Ajax request | |
if (craft()->request->isAjaxRequest()) { | |
craft()->request->close(); | |
} | |
// Start running tasks | |
craft()->tasks->runPendingTasks(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment