-
-
Save phil-quinn/aed842994e85fd79bf7efc6aa1c78455 to your computer and use it in GitHub Desktop.
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 | |
/* | |
Based On: | |
https://gist.github.com/muskie9/8321b9730c191d5c22b14ce2f316a1af | |
*/ | |
namespace Foo\Bar\Baz\Tasks; | |
use SilverStripe\Assets\File; | |
use SilverStripe\Core\ClassInfo; | |
use SilverStripe\Dev\BuildTask; | |
use SilverStripe\ORM\DataObject; | |
use SilverStripe\ORM\DB; | |
use SilverStripe\Versioned\Versioned; | |
use TractorCow\Fluent\Extension\FluentExtension; | |
use SilverStripe\Dev\Debug; | |
class SS4ContentFileMigrationTask extends BuildTask | |
{ | |
/** | |
* @var string | |
*/ | |
protected $title = 'SS4 Content File Migration Task'; | |
protected $dryRun = false; // set to false to write to DB | |
/** | |
* Mapping property for field updates | |
* | |
* @var array | |
*/ | |
private $mapping = []; | |
/** | |
* Cache file path lookups | |
* | |
* @var array | |
*/ | |
private $found_files = []; | |
/** | |
* Remember failed lookups for speed later | |
* | |
* @var array | |
*/ | |
private $missing_files = []; | |
/** | |
* @param \SilverStripe\Control\HTTPRequest $request | |
*/ | |
public function run($request) | |
{ | |
$this->setMapping(); | |
$this->migrateContentFiles(); | |
} | |
/** | |
* Migrate file image references to the new SS4 shortcode | |
*/ | |
protected function migrateContentFiles() | |
{ | |
foreach ($this->getMapping() as $class => $tables) { | |
foreach ($tables as $table => $fields) { | |
$selectString = 'ID'; | |
foreach ($fields as $field) { | |
$selectString = "$selectString, \"{$field}\""; | |
} | |
$records = DB::query("SELECT {$selectString} FROM \"{$table}\""); | |
//echo count($records) . " records to be updated in {$table}\n"; | |
foreach ($records as $record) { | |
//echo count($fields) . " field(s) to be updated for {$class} - {$record['ID']}\n"; | |
foreach ($fields as $field) { | |
$this->updateFileReference($class, $table, $record, $field); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Yield all subclasses of DataObject | |
* | |
* @return \Generator | |
*/ | |
protected function getClasses() | |
{ | |
$classes = ClassInfo::subclassesFor(DataObject::class); | |
unset($classes[strtolower(DataObject::class)]); | |
foreach ($classes as $class) { | |
yield $class; | |
} | |
} | |
/** | |
* Set mapping array | |
* | |
* [ | |
* 'ClassName' => [ | |
* 'TableName' => [ | |
* 'FieldName', | |
* 'FieldName2', | |
* ], | |
* 'TableName2' => [ | |
* 'FieldName3', | |
* ], | |
* ], | |
* ] | |
* | |
* @param $class | |
*/ | |
protected function setMapping() | |
{ | |
foreach ($this->getClasses() as $class) { | |
$classSingleton = $class::singleton(); | |
$versioned = $classSingleton->hasExtension(Versioned::class); | |
$localised = $classSingleton->hasExtension(FluentExtension::class); | |
foreach ($this->getDBFields($class) as $field => $type) { | |
if ($type == 'HTMLText') { | |
$table = $this->getFieldTable($class, $field); | |
if (!isset($this->mapping[$class])) { | |
$this->mapping[$class] = []; | |
} | |
// figure out which tables we will need | |
$tables = []; | |
$tables[] = $table; | |
// add in version tables if needed | |
if ($versioned) { | |
$tables[] = $table . '_Live'; | |
$tables[] = $table . '_Versions'; | |
} | |
// add in localised tables if needed | |
if ($localised) { | |
$localisedFields = $classSingleton->getLocalisedFields(); | |
if (count($localisedFields) && array_key_exists($field, $localisedFields)) { | |
$tables[] = $table . '_Localised'; | |
// add in localised version tables if needed | |
if ($versioned) { | |
$tables[] = $table . '_Localised_Live'; | |
$tables[] = $table . '_Localised_Versions'; | |
} | |
} | |
} | |
foreach ($tables as $table) { | |
if (!isset($this->mapping[$class][$table])) { | |
$this->mapping[$class][$table] = []; | |
} | |
if (!in_array($field, $this->mapping[$class][$table])) { | |
$this->mapping[$class][$table][] = $field; | |
} | |
} | |
} | |
} | |
} | |
// Debug::dump($this->mapping); | |
return $this; | |
} | |
/** | |
* Get the array of Class, Table and Field mapping for updating | |
* | |
* @return array | |
*/ | |
protected function getMapping() | |
{ | |
if (empty($this->mapping)) { | |
$this->setMapping(); | |
} | |
return $this->mapping; | |
} | |
/** | |
* Get database fields for a given class | |
* | |
* @param $class | |
* @return mixed | |
*/ | |
protected function getDBFields($class) | |
{ | |
return $class::singleton()->getSchema()->fieldSpecs($class); | |
} | |
/** | |
* Get the appropriate table to update via SQL as to not publish draft content from the SS3 site | |
* | |
* @param $class | |
* @param $field | |
* @return mixed | |
*/ | |
protected function getFieldTable($class, $field) | |
{ | |
return $class::singleton()->getSchema()->tableForField($class, $field); | |
} | |
/** | |
* Update records of based on ContentFileMigrationTask::mapping | |
* | |
* @param $class | |
* @param $table | |
* @param $record | |
* @param $field | |
*/ | |
protected function updateFileReference($class, $table, $record, $field) | |
{ | |
if (preg_match('/<img\s*(?:src\s*\=\s*[\'\"](.*?)[\'\"].*?\s*|\s*|\s*)+.*?>/sm', $record[$field], $matches)) { | |
foreach ($matches as $match) { | |
preg_match('/src="[^"]*"/', $match, $source); | |
if (count($source)) { | |
$path = substr($source[0], 5, strlen($source[0]) - 6); | |
$file = $this->findFile($path); | |
if ($file) { | |
// echo "## Found File: {$file->ID} - {$file->FileFilename}\n"; | |
$newValue = $this->getNewFieldValue($record, $field, $match, $file); | |
if ($newValue) { | |
if (!$this->dryRun) { | |
DB::prepared_query("UPDATE \"{$table}\" SET \"{$field}\" = ? WHERE ID = ?", [$newValue, $record['ID']]); | |
} | |
echo "Updated {$table} image reference for: {$class} - {$record['ID']}\n"; | |
} else { | |
echo "Couldn't update {$class} = {$record['ID']} - {$match}\n"; | |
} | |
} | |
else { | |
// echo "## Missing File: {$path}\n"; | |
} | |
} | |
} | |
} | |
} | |
protected function findFile($path) { | |
if (in_array($path, $this->missing_files)) { | |
return false; | |
} | |
else if (array_key_exists($path, $this->found_files)) { | |
return $this->found_files[$path]; | |
} | |
else { | |
// try the full path | |
$file = File::get()->filter('FileFilename:PartialMatch', $path)->first(); | |
// if that fails, try the filename | |
if (!$file) { | |
$parts = explode('/', $path); | |
$filename = $parts[count($parts) - 1]; | |
$file = File::get()->filter('FileFilename:PartialMatch', $filename)->first(); | |
} | |
// did we get one? | |
if ($file) { | |
// remember this one for next time | |
$this->found_files[$path] = $file; | |
return $file; | |
} | |
} | |
// we didn't find one, so remember our shame and failure | |
$this->missing_files[] = $path; | |
return false; | |
} | |
/** | |
* Replace image instances with new shortcode | |
* | |
* @param $record | |
* @param $field | |
* @param $match | |
* @param $file | |
* @return mixed | |
*/ | |
protected function getNewFieldValue($record, $field, $match, $file) | |
{ | |
if ($file instanceof File) { | |
$newPath = $file->getURL(); | |
$find = [ | |
'/<img/', | |
'/src=".*"/', | |
'/[a-zA-Z]+=""/', // delete empty attributes | |
'/>/', | |
]; | |
$replace = [ | |
'[image', | |
"src=\"{$newPath}\" id=\"{$file->ID}\"", | |
"", // delete empty attributes | |
']', | |
]; | |
$newReference = preg_replace($find, $replace, $match); | |
echo "New image referance {$newReference}\n"; | |
$newValue = str_replace($match, $newReference, $record[$field]); | |
return $newValue; | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment