Skip to content

Instantly share code, notes, and snippets.

@joshangell
Last active February 1, 2016 14:07
Show Gist options
  • Save joshangell/26d6413a1eb243d4e8a1 to your computer and use it in GitHub Desktop.
Save joshangell/26d6413a1eb243d4e8a1 to your computer and use it in GitHub Desktop.
Content migration in Craft example.
<?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]
));
}
}
<?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
);
}
}
<?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