Skip to content

Instantly share code, notes, and snippets.

@phil-quinn
Forked from muskie9/FileShortcodeTask.php
Last active April 15, 2019 22:12
Show Gist options
  • Save phil-quinn/aed842994e85fd79bf7efc6aa1c78455 to your computer and use it in GitHub Desktop.
Save phil-quinn/aed842994e85fd79bf7efc6aa1c78455 to your computer and use it in GitHub Desktop.
<?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