Merge branch '4.6' into 4.7

This commit is contained in:
Guy Marriott 2020-11-17 15:46:54 -08:00
commit b0762593da
No known key found for this signature in database
GPG Key ID: 1B28F911F4C43583

View File

@ -3,12 +3,14 @@
namespace SilverStripe\ORM; namespace SilverStripe\ORM;
use BadMethodCallException; use BadMethodCallException;
use Generator;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Environment; use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\DevelopmentAdmin; use SilverStripe\Dev\DevelopmentAdmin;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\Connect\DatabaseException; use SilverStripe\ORM\Connect\DatabaseException;
@ -38,17 +40,17 @@ class DatabaseAdmin extends Controller
* Obsolete classname values that should be remapped in dev/build * Obsolete classname values that should be remapped in dev/build
*/ */
private static $classname_value_remapping = [ private static $classname_value_remapping = [
'File' => 'SilverStripe\\Assets\\File', 'File' => 'SilverStripe\\Assets\\File',
'Image' => 'SilverStripe\\Assets\\Image', 'Image' => 'SilverStripe\\Assets\\Image',
'Folder' => 'SilverStripe\\Assets\\Folder', 'Folder' => 'SilverStripe\\Assets\\Folder',
'Group' => 'SilverStripe\\Security\\Group', 'Group' => 'SilverStripe\\Security\\Group',
'LoginAttempt' => 'SilverStripe\\Security\\LoginAttempt', 'LoginAttempt' => 'SilverStripe\\Security\\LoginAttempt',
'Member' => 'SilverStripe\\Security\\Member', 'Member' => 'SilverStripe\\Security\\Member',
'MemberPassword' => 'SilverStripe\\Security\\MemberPassword', 'MemberPassword' => 'SilverStripe\\Security\\MemberPassword',
'Permission' => 'SilverStripe\\Security\\Permission', 'Permission' => 'SilverStripe\\Security\\Permission',
'PermissionRole' => 'SilverStripe\\Security\\PermissionRole', 'PermissionRole' => 'SilverStripe\\Security\\PermissionRole',
'PermissionRoleCode' => 'SilverStripe\\Security\\PermissionRoleCode', 'PermissionRoleCode' => 'SilverStripe\\Security\\PermissionRoleCode',
'RememberLoginHash' => 'SilverStripe\\Security\\RememberLoginHash', 'RememberLoginHash' => 'SilverStripe\\Security\\RememberLoginHash',
]; ];
/** /**
@ -207,7 +209,7 @@ class DatabaseAdmin extends Controller
$file = TEMP_PATH $file = TEMP_PATH
. DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR
. 'database-last-generated-' . 'database-last-generated-'
. str_replace(['\\','/',':'], '.', Director::baseFolder()); . str_replace(['\\', '/', ':'], '.', Director::baseFolder());
if (file_exists($file)) { if (file_exists($file)) {
return filemtime($file); return filemtime($file);
@ -219,9 +221,9 @@ class DatabaseAdmin extends Controller
/** /**
* Updates the database schema, creating tables & fields as necessary. * Updates the database schema, creating tables & fields as necessary.
* *
* @param boolean $quiet Don't show messages * @param boolean $quiet Don't show messages
* @param boolean $populate Populate the database, as well as setting up its schema * @param boolean $populate Populate the database, as well as setting up its schema
* @param bool $testMode * @param bool $testMode
*/ */
public function doBuild($quiet = false, $populate = true, $testMode = false) public function doBuild($quiet = false, $populate = true, $testMode = false)
{ {
@ -356,15 +358,7 @@ class DatabaseAdmin extends Controller
} }
// Remap obsolete class names // Remap obsolete class names
$remappingConfig = $this->config()->get('classname_value_remapping'); $this->migrateClassNames();
$remappingFields = $this->getClassNameRemappingFields();
foreach ($remappingFields as $className => $fieldNames) {
foreach ($fieldNames as $fieldName) {
foreach ($remappingConfig as $oldClassName => $newClassName) {
$this->updateLegacyClassNames($className, $fieldName, $oldClassName, $newClassName);
}
}
}
// Require all default records // Require all default records
foreach ($dataClasses as $dataClass) { foreach ($dataClasses as $dataClass) {
@ -398,7 +392,7 @@ class DatabaseAdmin extends Controller
} }
if (!$quiet) { if (!$quiet) {
echo (Director::is_cli()) ? "\n Database build completed!\n\n" :"<p>Database build completed!</p>"; echo (Director::is_cli()) ? "\n Database build completed!\n\n" : "<p>Database build completed!</p>";
} }
foreach ($dataClasses as $dataClass) { foreach ($dataClasses as $dataClass) {
@ -416,8 +410,23 @@ class DatabaseAdmin extends Controller
* @param string $fieldName The field name to look in for obsolete class names * @param string $fieldName The field name to look in for obsolete class names
* @param string $oldClassName The old class name * @param string $oldClassName The old class name
* @param string $newClassName The new class name * @param string $newClassName The new class name
* @deprecated 5.0 use updateLegacyClassNameField instead
*/ */
protected function updateLegacyClassNames($dataClass, $fieldName, $oldClassName, $newClassName) protected function updateLegacyClassNames($dataClass, $fieldName, $oldClassName, $newClassName)
{
Deprecation::notice('5.0', 'use updateLegacyClassNameField instead');
$this->updateLegacyClassNameField($dataClass, $fieldName, [$oldClassName => $newClassName]);
}
/**
* Given a base data class, a field name and a mapping of class replacements, look for obsolete
* values in the $dataClass's $fieldName column and replace it with $mapping
*
* @param string $dataClass The data class to look up
* @param string $fieldName The field name to look in for obsolete class names
* @param string[] $mapping Map of old to new classnames
*/
protected function updateLegacyClassNameField($dataClass, $fieldName, $mapping)
{ {
$schema = DataObject::getSchema(); $schema = DataObject::getSchema();
// Check first to ensure that the class has the specified field to update // Check first to ensure that the class has the specified field to update
@ -426,36 +435,59 @@ class DatabaseAdmin extends Controller
} }
// Load a list of any records that have obsolete class names // Load a list of any records that have obsolete class names
$badRecordCount = DataObject::get($dataClass) $table = $schema->tableName($dataClass);
->filter([$fieldName => $oldClassName]) $currentClassNameList = DB::query("SELECT DISTINCT(\"{$fieldName}\") FROM \"{$table}\"")->column();
->count();
if (!$badRecordCount) { // Get all invalid classes for this field
$invalidClasses = array_intersect($currentClassNameList, array_keys($mapping));
if (!$invalidClasses) {
return; return;
} }
if (Director::is_cli()) { $numberClasses = count($invalidClasses);
echo " * Correcting {$badRecordCount} obsolete {$fieldName} values for {$newClassName}\n"; DB::alteration_message(
} else { "Correcting obsolete {$fieldName} values for {$numberClasses} outdated types",
echo "<li>Correcting {$badRecordCount} obsolete {$fieldName} values for {$newClassName}</li>\n"; 'obsolete'
);
// Build case assignment based on all intersected legacy classnames
$cases = [];
$params = [];
foreach ($invalidClasses as $invalidClass) {
$cases[] = "WHEN \"{$fieldName}\" = ? THEN ?";
$params[] = $invalidClass;
$params[] = $mapping[$invalidClass];
} }
foreach ($this->getClassTables($dataClass) as $table) {
$casesSQL = implode(' ', $cases);
$sql = "UPDATE \"{$table}\" SET \"{$fieldName}\" = CASE {$casesSQL} ELSE \"{$fieldName}\" END";
DB::prepared_query($sql, $params);
}
}
/**
* Get tables to update for this class
*
* @param string $dataClass
* @return Generator|string[]
*/
protected function getClassTables($dataClass)
{
$schema = DataObject::getSchema();
$table = $schema->tableName($dataClass); $table = $schema->tableName($dataClass);
$updateQuery = "UPDATE \"{$table}%s\" SET \"{$fieldName}\" = ? WHERE \"{$fieldName}\" = ?"; // Base table
$updateQueries = [sprintf($updateQuery, '')]; yield $table;
// Remap versioned table class name values as well // Remap versioned table class name values as well
/** @var Versioned|DataObject $class */ /** @var Versioned|DataObject $dataClass */
$class = DataObject::singleton($dataClass); $dataClass = DataObject::singleton($dataClass);
if ($class->hasExtension(Versioned::class)) { if ($dataClass->hasExtension(Versioned::class)) {
if ($class->hasStages()) { if ($dataClass->hasStages()) {
$updateQueries[] = sprintf($updateQuery, '_Live'); yield "{$table}_Live";
} }
$updateQueries[] = sprintf($updateQuery, '_Versions'); yield "{$table}_Versions";
}
foreach ($updateQueries as $query) {
DB::prepared_query($query, [$newClassName, $oldClassName]);
} }
} }
@ -535,4 +567,20 @@ class DatabaseAdmin extends Controller
} }
} }
} }
/**
* Migrate all class names
*
* @todo Migrate to separate build task
*/
protected function migrateClassNames()
{
$remappingConfig = $this->config()->get('classname_value_remapping');
$remappingFields = $this->getClassNameRemappingFields();
foreach ($remappingFields as $className => $fieldNames) {
foreach ($fieldNames as $fieldName) {
$this->updateLegacyClassNameField($className, $fieldName, $remappingConfig);
}
}
}
} }