public function updateSchema()
{
// Get the schema XML file
$xml = $this->findSchemaXml();
if (empty($xml)) {
return;
}
// Make sure there are SQL commands in this file
if (!$xml->sql) {
return;
}
// Walk the sql > action tags to find all tables
/** @var SimpleXMLElement $actions */
$actions = $xml->sql->children();
/**
* The meta/autocollation node defines if I should automatically apply the correct collation (utf8 or utf8mb4)
* to the database tables managed by the schema updater. When enabled (default) the queries are automatically
* converted to the correct collation (utf8mb4_unicode_ci or utf8_general_ci) depending on whether your Joomla!
* and MySQL server support Multibyte UTF-8 (UTF8MB4). Moreover, if UTF8MB4 is supported, all CREATE TABLE
* queries are analyzed and the tables referenced in them are auto-converted to the proper utf8mb4 collation.
*/
$autoCollationConversion = true;
if ($xml->meta->autocollation) {
$value = (string) $xml->meta->autocollation;
$value = trim($value);
$value = strtolower($value);
$autoCollationConversion = in_array($value, array('true', '1', 'on', 'yes'));
}
$hasUtf8mb4Support = method_exists($this->db, 'hasUTF8mb4Support') && $this->db->hasUTF8mb4Support();
$tablesToConvert = array();
// If we have an uppercase db prefix we can expect CREATE TABLE fail because we cannot detect reliably
// the existence of database tables. See https://github.com/joomla/joomla-cms/issues/10928#issuecomment-228549658
$prefix = $this->db->getPrefix();
$canFailCreateTable = preg_match('/[A-Z]/', $prefix);
/** @var SimpleXMLElement $action */
foreach ($actions as $action) {
// Get the attributes
$attributes = $action->attributes();
// Get the table / view name
$table = $attributes->table ? (string) $attributes->table : '';
if (empty($table)) {
continue;
}
// Am I allowed to let this action fail?
$canFailAction = $attributes->canfail ? $attributes->canfail : 0;
// Evaluate conditions
$shouldExecute = true;
/** @var SimpleXMLElement $node */
foreach ($action->children() as $node) {
if ($node->getName() == 'condition') {
// Get the operator
$operator = $node->attributes()->operator ? (string) $node->attributes()->operator : 'and';
$operator = empty($operator) ? 'and' : $operator;
$condition = $this->conditionMet($table, $node);
switch ($operator) {
case 'not':
$shouldExecute = $shouldExecute && !$condition;
break;
case 'or':
$shouldExecute = $shouldExecute || $condition;
break;
case 'nor':
$shouldExecute = !$shouldExecute && !$condition;
break;
case 'xor':
$shouldExecute = ($shouldExecute xor $condition);
break;
case 'maybe':
$shouldExecute = $condition ? true : $shouldExecute;
break;
default:
$shouldExecute = $shouldExecute && $condition;
break;
}
}
// DO NOT USE BOOLEAN SHORT CIRCUIT EVALUATION!
// if (!$shouldExecute) break;
}
// Do I have to only collect the tables from CREATE TABLE queries?
$onlyCollectTables = !$shouldExecute && $autoCollationConversion && $hasUtf8mb4Support;
// Make sure all conditions are met OR I have to collect tables from CREATE TABLE queries.
if (!$shouldExecute && !$onlyCollectTables) {
continue;
}
// Execute queries
foreach ($action->children() as $node) {
if ($node->getName() == 'query') {
$query = (string) $node;
if ($autoCollationConversion && $hasUtf8mb4Support) {
$this->extractTablesToConvert($query, $tablesToConvert);
}
// If we're only collecting tables do not run the queries
if ($onlyCollectTables) {
continue;
}
$canFail = $node->attributes->canfail ? (string) $node->attributes->canfail : $canFailAction;
if (is_string($canFail)) {
$canFail = strtoupper($canFail);
}
$canFail = in_array($canFail, array(true, 1, 'YES', 'TRUE'));
// Do I need to automatically convert the collation of all CREATE / ALTER queries?
if ($autoCollationConversion) {
if ($hasUtf8mb4Support) {
// We have UTF8MB4 support. Convert all queries to UTF8MB4.
$query = $this->convertUtf8QueryToUtf8mb4($query);
} else {
// We do not have UTF8MB4 support. Convert all queries to plain old UTF8.
$query = $this->convertUtf8mb4QueryToUtf8($query);
}
}
$this->db->setQuery($query);
try {
$this->db->execute();
} catch (Exception $e) {
// Special consideration for CREATE TABLE commands on uppercase prefix databases.
if ($canFailCreateTable && stripos($query, 'CREATE TABLE') !== false) {
$canFail = true;
}
// If we are not allowed to fail, throw back the exception we caught
if (!$canFail) {
throw $e;
}
}
}
}
}
// Auto-convert the collation of tables if we are told to do so, have utf8mb4 support and a list of tables.
if ($autoCollationConversion && $hasUtf8mb4Support && !empty($tablesToConvert)) {
$this->convertTablesToUtf8mb4($tablesToConvert);
}
}