File manager - Edit - /home/opticamezl/www/newok/Schema.tar
Back
ChangeItem/PostgresqlChangeItem.php 0000644 00000032676 15172701662 0013376 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Schema\ChangeItem; use Joomla\CMS\Schema\ChangeItem; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Checks the database schema against one PostgreSQL DDL query to see if it has been run. * * @since 3.0 */ class PostgresqlChangeItem extends ChangeItem { /** * Checks a DDL query to see if it is a known type * If yes, build a check query to see if the DDL has been run on the database. * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated. * The $msgElements contains the text to create the user message. * The $checkQuery contains the SQL query to check whether the schema change has * been run against the current database. The $queryType contains the type of * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX). * The $checkStatus field is set to zero if the query is created * * If not successful, $checkQuery is empty and , and $checkStatus is -1. * For example, this will happen if the current line is a non-DDL statement. * * @return void * * @since 3.0 */ protected function buildCheckQuery() { // Initialize fields in case we can't create a check query // Change status to skipped $this->checkStatus = -1; $result = null; $splitIntoWords = "~'[^']*'(*SKIP)(*F)|\s+~"; $splitIntoActions = "~'[^']*'(*SKIP)(*F)|\([^)]*\)(*SKIP)(*F)|,~"; // Remove any newlines $this->updateQuery = str_replace("\n", '', $this->updateQuery); // Remove trailing whitespace and semicolon $this->updateQuery = rtrim($this->updateQuery, "; \t\n\r\0\x0B"); // Fix up extra spaces around () and in general $find = ['#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#']; $replace = ['($3)', '$1']; $updateQuery = preg_replace($find, $replace, $this->updateQuery); $wordArray = preg_split($splitIntoWords, $updateQuery, -1, PREG_SPLIT_NO_EMPTY); $totalWords = \count($wordArray); // First, make sure we have an array of at least 6 elements // if not, we can't make a check query for this one if ($totalWords < 6) { // Done with method return; } // We can only make check queries for alter table and create table queries $command = strtoupper($wordArray[0] . ' ' . $wordArray[1]); if ($command === 'ALTER TABLE') { // Check only the last action $actions = ltrim(substr($updateQuery, strpos($updateQuery, $wordArray[2]) + \strlen($wordArray[2]))); $actions = preg_split($splitIntoActions, $actions); // Get the last action $lastActionArray = preg_split($splitIntoWords, end($actions), -1, PREG_SPLIT_NO_EMPTY); // Replace all actions by the last one array_splice($wordArray, 3, $totalWords, $lastActionArray); $alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]); if ($alterCommand === 'RENAME TO') { $table = $this->fixQuote($wordArray[5]); $result = 'SELECT table_name FROM information_schema.tables WHERE table_name=' . $table; $this->queryType = 'RENAME_TABLE'; $this->checkQueryExpected = 1; $this->msgElements = [$table]; } elseif ($alterCommand === 'ADD COLUMN') { $result = 'SELECT column_name' . ' FROM information_schema.columns' . ' WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]); $this->queryType = 'ADD_COLUMN'; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), ]; } elseif ($alterCommand === 'DROP COLUMN') { $result = 'SELECT column_name' . ' FROM information_schema.columns' . ' WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]); $this->queryType = 'DROP_COLUMN'; $this->checkQueryExpected = 0; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), ]; } elseif ($alterCommand === 'ALTER COLUMN') { $alterAction = strtoupper($wordArray[6]); if ($alterAction === 'TYPE') { $type = implode(' ', \array_slice($wordArray, 7)); if ($pos = stripos($type, ' USING ')) { $type = substr($type, 0, $pos); } if ($pos = strpos($type, '(')) { $datatype = substr($type, 0, $pos); } else { $datatype = $type; } if ($datatype === 'varchar') { $datatype = 'character varying'; } $result = 'SELECT column_name, data_type ' . 'FROM information_schema.columns WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]) . ' AND data_type=' . $this->fixQuote($datatype); if ($datatype === 'character varying') { $result .= ' AND character_maximum_length = ' . (int) substr($type, $pos + 1); } $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), $type, ]; } elseif ($alterAction === 'SET') { $alterType = strtoupper($wordArray[7]); if ($alterType === 'NOT' && strtoupper($wordArray[8]) === 'NULL') { $result = 'SELECT column_name, data_type, is_nullable' . ' FROM information_schema.columns' . ' WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]) . ' AND is_nullable=' . $this->fixQuote('NO'); $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), 'NOT NULL', ]; } elseif ($alterType === 'DEFAULT') { $result = 'SELECT column_name, data_type, is_nullable' . ' FROM information_schema.columns' . ' WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]) . ' AND (CASE (position(' . $this->db->quote('::') . ' in column_default))' . ' WHEN 0 THEN ' . ' column_default = ' . $this->db->quote($wordArray[8]) . ' ELSE ' . ' substring(column_default, 1, (position(' . $this->db->quote('::') . ' in column_default) -1)) = ' . $this->db->quote($wordArray[8]) . ' END)'; $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), 'DEFAULT ' . $wordArray[8], ]; } } elseif ($alterAction === 'DROP') { $alterType = strtoupper($wordArray[7]); if ($alterType === 'DEFAULT') { $result = 'SELECT column_name, data_type, is_nullable , column_default' . ' FROM information_schema.columns' . ' WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]) . ' AND column_default IS NOT NULL'; $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->checkQueryExpected = 0; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), 'NOT DEFAULT', ]; } elseif ($alterType === 'NOT' && strtoupper($wordArray[8]) === 'NULL') { $result = 'SELECT column_name, data_type, is_nullable , column_default' . ' FROM information_schema.columns' . ' WHERE table_name=' . $this->fixQuote($wordArray[2]) . ' AND column_name=' . $this->fixQuote($wordArray[5]) . ' AND is_nullable = ' . $this->fixQuote('NO'); $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->checkQueryExpected = 0; $this->msgElements = [ $this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), 'NULL', ]; } } } } elseif ($command === 'DROP INDEX') { if (strtoupper($wordArray[2] . $wordArray[3]) === 'IFEXISTS') { $idx = $this->fixQuote($wordArray[4]); } else { $idx = $this->fixQuote($wordArray[2]); } $result = 'SELECT * FROM pg_indexes WHERE indexname=' . $idx; $this->queryType = 'DROP_INDEX'; $this->checkQueryExpected = 0; $this->msgElements = [$this->fixQuote($idx)]; } elseif ($command === 'CREATE INDEX' || (strtoupper($command . $wordArray[2]) === 'CREATE UNIQUE INDEX')) { if ($wordArray[1] === 'UNIQUE') { $idx = $this->fixQuote($wordArray[3]); $table = $this->fixQuote($wordArray[5]); } else { $idx = $this->fixQuote($wordArray[2]); $table = $this->fixQuote($wordArray[4]); } $result = 'SELECT * FROM pg_indexes WHERE indexname=' . $idx . ' AND tablename=' . $table; $this->queryType = 'ADD_INDEX'; $this->checkQueryExpected = 1; $this->msgElements = [$table, $idx]; } if ($command === 'CREATE TABLE') { if (strtoupper($wordArray[2] . $wordArray[3] . $wordArray[4]) === 'IFNOTEXISTS') { $table = $this->fixQuote($wordArray[5]); } else { $table = $this->fixQuote($wordArray[2]); } $result = 'SELECT table_name FROM information_schema.tables WHERE table_name=' . $table; $this->queryType = 'CREATE_TABLE'; $this->checkQueryExpected = 1; $this->msgElements = [$table]; } // Set fields based on results if ($this->checkQuery = $result) { // Unchecked status $this->checkStatus = 0; } else { // Skipped $this->checkStatus = -1; } } /** * Fix up integer. Fixes problem with PostgreSQL integer descriptions. * If you change a column to "integer unsigned" it shows * as "int(10) unsigned" in the check query. * * @param string $type1 the column type * @param string $type2 the column attributes * * @return string The original or changed column type. * * @since 3.0 */ private function fixInteger($type1, $type2) { $result = $type1; if (strtolower($type1) === 'integer' && strtolower(substr($type2, 0, 8)) === 'unsigned') { $result = 'unsigned int(10)'; } return $result; } /** * Fixes up a string for inclusion in a query. * Replaces name quote character with normal quote for literal. * Drops trailing semicolon. Injects the database prefix. * * @param string $string The input string to be cleaned up. * * @return string The modified string. * * @since 3.0 */ private function fixQuote($string) { $string = str_replace('"', '', $string); $string = str_replace(';', '', $string); $string = str_replace('#__', $this->db->getPrefix(), $string); return $this->db->quote($string); } } ChangeItem/MysqlChangeItem.php 0000644 00000035653 15172701662 0012336 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Schema\ChangeItem; use Joomla\CMS\Schema\ChangeItem; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Checks the database schema against one MySQL DDL query to see if it has been run. * * @since 2.5 */ class MysqlChangeItem extends ChangeItem { /** * Checks a DDL query to see if it is a known type * If yes, build a check query to see if the DDL has been run on the database. * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated. * The $msgElements contains the text to create the user message. * The $checkQuery contains the SQL query to check whether the schema change has * been run against the current database. The $queryType contains the type of * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX). * The $checkStatus field is set to zero if the query is created * * If not successful, $checkQuery is empty and , and $checkStatus is -1. * For example, this will happen if the current line is a non-DDL statement. * * @return void * * @since 2.5 */ protected function buildCheckQuery() { // Initialize fields in case we can't create a check query // Change status to skipped $this->checkStatus = -1; $result = null; // Remove any newlines $this->updateQuery = str_replace("\n", '', $this->updateQuery); // Fix up extra spaces around () and in general $find = ['#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#']; $replace = ['($3)', '$1']; $updateQuery = preg_replace($find, $replace, $this->updateQuery); $wordArray = preg_split("~'[^']*'(*SKIP)(*F)|\s+~u", trim($updateQuery, "; \t\n\r\0\x0B")); // First, make sure we have an array of at least 5 elements // if not, we can't make a check query for this one if (\count($wordArray) < 5) { // Done with method return; } // We can only make check queries for rename table, alter table and create table queries $command = strtoupper($wordArray[0] . ' ' . $wordArray[1]); if ($command === 'RENAME TABLE') { $table = $this->fixQuote($wordArray[4]); $this->checkQuery = 'SHOW TABLES LIKE ' . $table; $this->queryType = 'RENAME_TABLE'; $this->msgElements = [$table]; $this->checkStatus = 0; // Done with method return; } // For the remaining query types make sure we have an array of at least 6 elements if (\count($wordArray) < 6) { // Done with method return; } if ($command === 'ALTER TABLE') { $alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]); if ($alterCommand === 'ADD COLUMN') { $result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' . $this->fixQuote($wordArray[5]); $this->queryType = 'ADD_COLUMN'; $this->msgElements = [$this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5])]; } elseif ($alterCommand === 'ADD INDEX' || $alterCommand === 'ADD KEY') { if ($pos = strpos($wordArray[5], '(')) { $index = $this->fixQuote(substr($wordArray[5], 0, $pos)); } else { $index = $this->fixQuote($wordArray[5]); } $result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index; $this->queryType = 'ADD_INDEX'; $this->msgElements = [$this->fixQuote($wordArray[2]), $index]; } elseif ($alterCommand === 'ADD UNIQUE') { $idxIndexName = 5; if (isset($wordArray[6])) { $addCmdCheck = strtoupper($wordArray[5]); if ($addCmdCheck === 'INDEX' || $addCmdCheck === 'KEY') { $idxIndexName = 6; } } if ($pos = strpos($wordArray[$idxIndexName], '(')) { $index = $this->fixQuote(substr($wordArray[$idxIndexName], 0, $pos)); } else { $index = $this->fixQuote($wordArray[$idxIndexName]); } $result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index; $this->queryType = 'ADD_INDEX'; $this->msgElements = [$this->fixQuote($wordArray[2]), $index]; } elseif ($alterCommand === 'DROP INDEX' || $alterCommand === 'DROP KEY') { $index = $this->fixQuote($wordArray[5]); $result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index; $this->queryType = 'DROP_INDEX'; $this->checkQueryExpected = 0; $this->msgElements = [$this->fixQuote($wordArray[2]), $index]; } elseif ($alterCommand === 'DROP COLUMN') { $index = $this->fixQuote($wordArray[5]); $result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE Field = ' . $index; $this->queryType = 'DROP_COLUMN'; $this->checkQueryExpected = 0; $this->msgElements = [$this->fixQuote($wordArray[2]), $index]; } elseif (strtoupper($wordArray[3]) === 'MODIFY') { // Kludge to fix problem with "integer unsigned" $type = $wordArray[5]; if (isset($wordArray[6])) { $type = $this->fixInteger($wordArray[5], $wordArray[6]); } // Detect changes in NULL and in DEFAULT column attributes $changesArray = \array_slice($wordArray, 6); $defaultCheck = $this->checkDefault($changesArray, $type); $nullCheck = $this->checkNull($changesArray); /** * When we made the UTF8MB4 conversion then text becomes medium text - so loosen the checks to these two types * otherwise (for example) the profile fields profile_value check fails - see https://github.com/joomla/joomla-cms/issues/9258 */ $typeCheck = $this->fixUtf8mb4TypeChecks($type); $result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' . $this->fixQuote($wordArray[4]) . ' AND ' . $typeCheck . ($defaultCheck ? ' AND ' . $defaultCheck : '') . ($nullCheck ? ' AND ' . $nullCheck : ''); $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->msgElements = [$this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[4]), $type]; } elseif (strtoupper($wordArray[3]) === 'CHANGE') { // Kludge to fix problem with "integer unsigned" $type = $wordArray[6]; if (isset($wordArray[7])) { $type = $this->fixInteger($wordArray[6], $wordArray[7]); } // Detect changes in NULL and in DEFAULT column attributes $changesArray = \array_slice($wordArray, 6); $defaultCheck = $this->checkDefault($changesArray, $type); $nullCheck = $this->checkNull($changesArray); /** * When we made the UTF8MB4 conversion then text becomes medium text - so loosen the checks to these two types * otherwise (for example) the profile fields profile_value check fails - see https://github.com/joomla/joomla-cms/issues/9258 */ $typeCheck = $this->fixUtf8mb4TypeChecks($type); $result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' . $this->fixQuote($wordArray[5]) . ' AND ' . $typeCheck . ($defaultCheck ? ' AND ' . $defaultCheck : '') . ($nullCheck ? ' AND ' . $nullCheck : ''); $this->queryType = 'CHANGE_COLUMN_TYPE'; $this->msgElements = [$this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), $type]; } } if ($command === 'CREATE TABLE') { if (strtoupper($wordArray[2] . $wordArray[3] . $wordArray[4]) === 'IFNOTEXISTS') { $table = $wordArray[5]; } else { $table = $wordArray[2]; } $result = 'SHOW TABLES LIKE ' . $this->fixQuote($table); $this->queryType = 'CREATE_TABLE'; $this->msgElements = [$this->fixQuote($table)]; } // Set fields based on results if ($this->checkQuery = $result) { // Unchecked status $this->checkStatus = 0; } else { // Skipped $this->checkStatus = -1; } } /** * Fix up integer. Fixes problem with MySQL integer descriptions. * On MySQL 8 display length is not shown anymore. * This means we have to match e.g. both "int(10) unsigned" and * "int unsigned", or both "int(11)" and "int" and so on. * The same applies to the other integer data types "tinyint", * "smallint", "mediumint" and "bigint". * * @param string $type1 the column type * @param string $type2 the column attributes * * @return string The original or changed column type. * * @since 2.5 */ private function fixInteger($type1, $type2) { $result = $type1; if (preg_match('/^(?P<type>(big|medium|small|tiny)?int)(\([0-9]+\))?$/i', $type1, $matches)) { $result = strtolower($matches['type']); } if (strtolower(substr($type2, 0, 8)) === 'unsigned') { $result .= ' unsigned'; } return $result; } /** * Fixes up a string for inclusion in a query. * Replaces name quote character with normal quote for literal. * Drops trailing semicolon. Injects the database prefix. * * @param string $string The input string to be cleaned up. * * @return string The modified string. * * @since 2.5 */ private function fixQuote($string) { $string = str_replace('`', '', $string); $string = str_replace(';', '', $string); $string = str_replace('#__', $this->db->getPrefix(), $string); return $this->db->quote($string); } /** * Make check query for column changes/modifications tolerant * for automatic type changes of text columns, e.g. from TEXT * to MEDIUMTEXT, after conversion from utf8 to utf8mb4, and * fix integer columns without display length for MySQL 8 * (see also function "fixInteger" above). * * @param string $type The column type found in the update query * * @return string The condition for type check in the check query * * @since 3.5 */ private function fixUtf8mb4TypeChecks($type) { $uType = strtoupper(str_replace(';', '', $type)); switch ($uType) { case 'BIGINT UNSIGNED': case 'INT UNSIGNED': case 'MEDIUMINT UNSIGNED': case 'SMALLINT UNSIGNED': case 'TINYINT UNSIGNED': // Eg for "INT": "UPPER(type) REGEXP '^INT([(][0-9]+[)])? UNSIGNED$'" $typeCheck = 'UPPER(type) REGEXP ' . $this->db->quote('^' . str_replace(' ', '([(][0-9]+[)])? ', $uType) . '$'); break; case 'BIGINT': case 'INT': case 'MEDIUMINT': case 'SMALLINT': case 'TINYINT': // Eg for "INT": "UPPER(type) REGEXP '^INT([(][0-9]+[)])?$'" $typeCheck = 'UPPER(type) REGEXP ' . $this->db->quote('^' . $uType . '([(][0-9]+[)])?$'); break; case 'MEDIUMTEXT': $typeCheck = $this->db->hasUTF8mb4Support() ? 'UPPER(type) IN (' . $this->db->quote('MEDIUMTEXT') . ',' . $this->db->quote('LONGTEXT') . ')' : 'UPPER(type) = ' . $this->db->quote('MEDIUMTEXT'); break; case 'TEXT': $typeCheck = $this->db->hasUTF8mb4Support() ? 'UPPER(type) IN (' . $this->db->quote('TEXT') . ',' . $this->db->quote('MEDIUMTEXT') . ')' : 'UPPER(type) = ' . $this->db->quote('TEXT'); break; case 'TINYTEXT': $typeCheck = $this->db->hasUTF8mb4Support() ? 'UPPER(type) IN (' . $this->db->quote('TINYTEXT') . ',' . $this->db->quote('TEXT') . ')' : 'UPPER(type) = ' . $this->db->quote('TINYTEXT'); break; default: $typeCheck = 'UPPER(type) = ' . $this->db->quote($uType); } return $typeCheck; } /** * Create query clause for column changes/modifications for NULL attribute * * @param array $changesArray The array of words after COLUMN name * * @return string The query clause for NULL check in the check query * * @since 3.8.6 */ private function checkNull($changesArray) { // Find NULL keyword $index = array_search('null', array_map('strtolower', $changesArray)); // Create the check if ($index !== false) { if ($index == 0 || strtolower($changesArray[$index - 1]) !== 'not') { return ' `null` = ' . $this->db->quote('YES'); } else { return ' `null` = ' . $this->db->quote('NO'); } } return false; } /** * Create query clause for column changes/modifications for DEFAULT attribute * * @param array $changesArray The array of words after COLUMN name * @param string $type The type of the COLUMN * * @return string The query clause for DEFAULT check in the check query * * @since 3.8.6 */ private function checkDefault($changesArray, $type) { // Skip types that do not support default values $type = strtolower($type); if (substr($type, -4) === 'text' || substr($type, -4) === 'blob') { return false; } // Find DEFAULT keyword $index = array_search('default', array_map('strtolower', $changesArray)); // Create the check if ($index !== false) { if (strtolower($changesArray[$index + 1]) === 'null') { return ' `default` IS NULL'; } else { return ' `default` = ' . $changesArray[$index + 1]; } } return false; } } ChangeItem/SqlsrvChangeItem.php 0000644 00000013271 15172701662 0012513 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Schema\ChangeItem; use Joomla\CMS\Schema\ChangeItem; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Checks the database schema against one SQL Server DDL query to see if it has been run. * * @since 2.5 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ class SqlsrvChangeItem extends ChangeItem { /** * Checks a DDL query to see if it is a known type * If yes, build a check query to see if the DDL has been run on the database. * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated. * The $msgElements contains the text to create the user message. * The $checkQuery contains the SQL query to check whether the schema change has * been run against the current database. The $queryType contains the type of * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX). * The $checkStatus field is set to zero if the query is created * * If not successful, $checkQuery is empty and , and $checkStatus is -1. * For example, this will happen if the current line is a non-DDL statement. * * @return void * * @since 2.5 */ protected function buildCheckQuery() { // Initialize fields in case we can't create a check query // Change status to skipped $this->checkStatus = -1; $result = null; // Remove any newlines $this->updateQuery = str_replace("\n", '', $this->updateQuery); // Fix up extra spaces around () and in general $find = ['#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#']; $replace = ['($3)', '$1']; $updateQuery = preg_replace($find, $replace, $this->updateQuery); $wordArray = explode(' ', $updateQuery); // First, make sure we have an array of at least 6 elements // if not, we can't make a check query for this one if (\count($wordArray) < 6) { // Done with method return; } // We can only make check queries for alter table and create table queries $command = strtoupper($wordArray[0] . ' ' . $wordArray[1]); if ($command === 'ALTER TABLE') { $alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]); if ($alterCommand === 'ADD') { $result = 'SELECT * FROM INFORMATION_SCHEMA.Columns ' . $wordArray[2] . ' WHERE COLUMN_NAME = ' . $this->fixQuote($wordArray[5]); $this->queryType = 'ADD'; $this->msgElements = [$this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5])]; } elseif ($alterCommand === 'CREATE INDEX') { $index = $this->fixQuote(substr($wordArray[5], 0, strpos($wordArray[5], '('))); $result = 'SELECT * FROM SYS.INDEXES ' . $wordArray[2] . ' WHERE name = ' . $index; $this->queryType = 'CREATE INDEX'; $this->msgElements = [$this->fixQuote($wordArray[2]), $index]; } elseif (strtoupper($wordArray[3]) === 'MODIFY' || strtoupper($wordArray[3]) === 'CHANGE') { $result = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ' . $this->fixQuote($wordArray[2]); $this->queryType = 'ALTER COLUMN COLUMN_NAME =' . $this->fixQuote($wordArray[4]); $this->msgElements = [$this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[4])]; } } if ($command === 'CREATE TABLE') { $table = $wordArray[2]; $result = 'SELECT * FROM sys.TABLES WHERE NAME = ' . $this->fixQuote($table); $this->queryType = 'CREATE_TABLE'; $this->msgElements = [$this->fixQuote($table)]; } // Set fields based on results if ($this->checkQuery = $result) { // Unchecked status $this->checkStatus = 0; } else { // Skipped $this->checkStatus = -1; } } /** * Fix up integer. Fixes problem with MySQL integer descriptions. * If you change a column to "integer unsigned" it shows * as "int(10) unsigned" in the check query. * * @param string $type1 the column type * @param string $type2 the column attributes * * @return string The original or changed column type. * * @since 2.5 */ private function fixInteger($type1, $type2) { $result = $type1; if (strtolower($type1) === 'integer' && strtolower(substr($type2, 0, 8)) === 'unsigned') { $result = 'int'; } return $result; } /** * Fixes up a string for inclusion in a query. * Replaces name quote character with normal quote for literal. * Drops trailing semicolon. Injects the database prefix. * * @param string $string The input string to be cleaned up. * * @return string The modified string. * * @since 2.5 */ private function fixQuote($string) { $string = str_replace('[', '', $string); $string = str_replace(']', '', $string); $string = str_replace('"', '', $string); $string = str_replace(';', '', $string); $string = str_replace('#__', $this->db->getPrefix(), $string); return $this->db->quote($string); } } ChangeSet.php 0000644 00000021765 15172701662 0007140 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Schema; use Joomla\CMS\Filesystem\Folder; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Contains a set of JSchemaChange objects for a particular instance of Joomla. * Each of these objects contains a DDL query that should have been run against * the database when this database was created or updated. This enables the * Installation Manager to check that the current database schema is up to date. * * @since 2.5 */ class ChangeSet { /** * Array of ChangeItem objects * * @var ChangeItem[] * @since 2.5 */ protected $changeItems = []; /** * DatabaseDriver object * * @var DatabaseDriver * @since 2.5 */ protected $db = null; /** * Folder where SQL update files will be found * * @var string * @since 2.5 */ protected $folder = null; /** * The singleton instance of this object * * @var ChangeSet * @since 3.5.1 */ protected static $instance; /** * Constructor: builds array of $changeItems by processing the .sql files in a folder. * The folder for the Joomla core updates is `administrator/components/com_admin/sql/updates/<database>`. * * @param DatabaseDriver $db The current database object * @param string $folder The full path to the folder containing the update queries * * @since 2.5 */ public function __construct($db, $folder = null) { $this->db = $db; $this->folder = $folder; $updateFiles = $this->getUpdateFiles(); // If no files were found nothing more we can do - continue if ($updateFiles === false) { return; } $updateQueries = $this->getUpdateQueries($updateFiles); foreach ($updateQueries as $obj) { $this->changeItems[] = ChangeItem::getInstance($db, $obj->file, $obj->updateQuery); } // If on mysql, add a query at the end to check for utf8mb4 conversion status if ($this->db->getServerType() === 'mysql') { // Check if the #__utf8_conversion table exists $this->db->setQuery('SHOW TABLES LIKE ' . $this->db->quote($this->db->getPrefix() . 'utf8_conversion')); try { $rows = $this->db->loadRowList(0); $tableExists = \count($rows); } catch (\RuntimeException $e) { $tableExists = 0; } // If the table exists add a change item for utf8mb4 conversion to the end if ($tableExists > 0) { // Let the update query do nothing $tmpSchemaChangeItem = ChangeItem::getInstance( $db, 'database.php', 'UPDATE ' . $this->db->quoteName('#__utf8_conversion') . ' SET ' . $this->db->quoteName('converted') . ' = ' . $this->db->quoteName('converted') . ';' ); // Set to not skipped $tmpSchemaChangeItem->checkStatus = 0; // Set the check query $tmpSchemaChangeItem->queryType = 'UTF8_CONVERSION_UTF8MB4'; $tmpSchemaChangeItem->checkQuery = 'SELECT ' . $this->db->quoteName('converted') . ' FROM ' . $this->db->quoteName('#__utf8_conversion') . ' WHERE ' . $this->db->quoteName('converted') . ' = 5'; // Set expected records from check query $tmpSchemaChangeItem->checkQueryExpected = 1; $tmpSchemaChangeItem->msgElements = []; $this->changeItems[] = $tmpSchemaChangeItem; } } } /** * Returns a reference to the ChangeSet object, only creating it if it doesn't already exist. * * @param DatabaseDriver $db The current database object * @param string $folder The full path to the folder containing the update queries * * @return ChangeSet * * @since 2.5 */ public static function getInstance($db, $folder = null) { if (!\is_object(static::$instance)) { static::$instance = new static($db, $folder); } return static::$instance; } /** * Checks the database and returns an array of any errors found. * Note these are not database errors but rather situations where * the current schema is not up to date. * * @return array Array of errors if any. * * @since 2.5 */ public function check() { $errors = []; foreach ($this->changeItems as $item) { if ($item->check() === -2) { // Error found $errors[] = $item; } } return $errors; } /** * Runs the update query to apply the change to the database * * @return void * * @since 2.5 */ public function fix() { $this->check(); foreach ($this->changeItems as $item) { $item->fix(); } } /** * Returns an array of results for this set * * @return array associative array of changeitems grouped by unchecked, ok, error, and skipped * * @since 2.5 */ public function getStatus() { $result = ['unchecked' => [], 'ok' => [], 'error' => [], 'skipped' => []]; foreach ($this->changeItems as $item) { switch ($item->checkStatus) { case 0: $result['unchecked'][] = $item; break; case 1: $result['ok'][] = $item; break; case -2: $result['error'][] = $item; break; case -1: $result['skipped'][] = $item; break; } } return $result; } /** * Gets the current database schema, based on the highest version number. * Note that the .sql files are named based on the version and date, so * the file name of the last file should match the database schema version * in the #__schemas table. * * @return string the schema version for the database * * @since 2.5 */ public function getSchema() { $updateFiles = $this->getUpdateFiles(); // No schema files found - stop and return empty string if (empty($updateFiles)) { return ''; } $result = new \SplFileInfo(array_pop($updateFiles)); return $result->getBasename('.sql'); } /** * Get list of SQL update files for this database * * @return array|boolean list of sql update full-path names. False if directory doesn't exist * * @since 2.5 */ private function getUpdateFiles() { // Get the folder from the database name $sqlFolder = $this->db->getServerType(); // For `mssql` server types, convert the type to `sqlazure` if ($sqlFolder === 'mssql') { $sqlFolder = 'sqlazure'; } // Default folder to core com_admin if (!$this->folder) { $this->folder = JPATH_ADMINISTRATOR . '/components/com_admin/sql/updates/'; } // We don't want to enqueue an error if the directory doesn't exist - this can be handled elsewhere/ // So bail here. if (!is_dir($this->folder . '/' . $sqlFolder)) { return []; } return Folder::files( $this->folder . '/' . $sqlFolder, '\.sql$', 1, true, ['.svn', 'CVS', '.DS_Store', '__MACOSX'], ['^\..*', '.*~'], true ); } /** * Get array of SQL queries * * @param array $sqlfiles Array of .sql update filenames. * * @return array Array of \stdClass objects where: * file=filename, * update_query = text of SQL update query * * @since 2.5 */ private function getUpdateQueries(array $sqlfiles) { // Hold results as array of objects $result = []; foreach ($sqlfiles as $file) { $buffer = file_get_contents($file); // Create an array of queries from the sql file $queries = DatabaseDriver::splitSql($buffer); foreach ($queries as $query) { $fileQueries = new \stdClass(); $fileQueries->file = $file; $fileQueries->updateQuery = $query; $result[] = $fileQueries; } } return $result; } } ChangeItem.php 0000644 00000016622 15172701662 0007277 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Schema; use Joomla\CMS\Factory; use Joomla\Database\DatabaseDriver; use Joomla\Database\Exception\ExecutionFailureException; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Each object represents one query, which is one line from a DDL SQL query. * This class is used to check the site's database to see if the DDL query has been run. * If not, it provides the ability to fix the database by re-running the DDL query. * The queries are parsed from the update files in the folder * `administrator/components/com_admin/sql/updates/<database>`. * These updates are run automatically if the site was updated using com_installer. * However, it is possible that the program files could be updated without updating * the database (for example, if a user just copies the new files over the top of an * existing installation). * * This is an abstract class. We need to extend it for each database and add a * buildCheckQuery() method that creates the query to check that a DDL query has been run. * * @since 2.5 */ abstract class ChangeItem { /** * Update file: full path file name where query was found * * @var string * @since 2.5 */ public $file = null; /** * Update query: query used to change the db schema (one line from the file) * * @var string * @since 2.5 */ public $updateQuery = null; /** * Check query: query used to check the db schema * * @var string * @since 2.5 */ public $checkQuery = null; /** * Check query result: expected result of check query if database is up to date * * @var string * @since 2.5 */ public $checkQueryExpected = 1; /** * DatabaseDriver object * * @var DatabaseDriver * @since 2.5 */ public $db = null; /** * Query type: To be used in building a language key for a * message to tell user what was checked / changed * Possible values: ADD_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX * * @var string * @since 2.5 */ public $queryType = null; /** * Array with values for use in a Text::sprintf statement indicating what was checked * * Tells you what the message should be, based on which elements are defined, as follows: * For ADD_TABLE: table * For ADD_COLUMN: table, column * For CHANGE_COLUMN_TYPE: table, column, type * For ADD_INDEX: table, index * * @var array * @since 2.5 */ public $msgElements = []; /** * Checked status * * @var integer 0=not checked, -1=skipped, -2=failed, 1=succeeded * @since 2.5 */ public $checkStatus = 0; /** * Rerun status * * @var integer 0=not rerun, -1=skipped, -2=failed, 1=succeeded * @since 2.5 */ public $rerunStatus = 0; /** * Constructor: builds check query and message from $updateQuery * * @param DatabaseDriver $db Database connector object * @param string $file Full path name of the sql file * @param string $query Text of the sql query (one line of the file) * * @since 2.5 */ public function __construct($db, $file, $query) { $this->updateQuery = $query; $this->file = $file; $this->db = $db; $this->buildCheckQuery(); } /** * Returns a reference to the ChangeItem object. * * @param DatabaseDriver $db Database connector object * @param string $file Full path name of the sql file * @param string $query Text of the sql query (one line of the file) * * @return ChangeItem instance based on the database driver * * @since 2.5 * @throws \RuntimeException if class for database driver not found */ public static function getInstance($db, $file, $query) { // Get the class name $serverType = $db->getServerType(); // For `mssql` server types, convert the type to `sqlsrv` if ($serverType === 'mssql') { $serverType = 'sqlsrv'; } $class = '\\Joomla\\CMS\\Schema\\ChangeItem\\' . ucfirst($serverType) . 'ChangeItem'; // If the class exists, return it. if (class_exists($class)) { return new $class($db, $file, $query); } throw new \RuntimeException(sprintf('ChangeItem child class not found for the %s database driver', $serverType), 500); } /** * Checks a DDL query to see if it is a known type * If yes, build a check query to see if the DDL has been run on the database. * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated. * The $msgElements contains the text to create the user message. * The $checkQuery contains the SQL query to check whether the schema change has * been run against the current database. The $queryType contains the type of * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX). * The $checkStatus field is set to zero if the query is created * * If not successful, $checkQuery is empty and , and $checkStatus is -1. * For example, this will happen if the current line is a non-DDL statement. * * @return void * * @since 2.5 */ abstract protected function buildCheckQuery(); /** * Runs the check query and checks that 1 row is returned * * @return integer 1 if success, -1 if skipped, -2 if check failed * * @since 2.5 */ public function check() { $this->checkStatus = -1; if ($this->checkQuery) { try { $this->db->setQuery($this->checkQuery); $rows = $this->db->loadRowList(0); } catch (\RuntimeException $e) { // Still render the error message from the Exception object Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); $this->checkStatus = -2; return $this->checkStatus; } if (\count($rows) === $this->checkQueryExpected) { $this->checkStatus = 1; return $this->checkStatus; } $this->checkStatus = -2; } return $this->checkStatus; } /** * Runs the update query to apply the change to the database * * @return void * * @since 2.5 */ public function fix() { if ($this->checkStatus === -2) { // At this point we have a failed query $query = $this->updateQuery; try { $this->db->setQuery($query); $this->db->execute(); if ($this->check()) { $this->checkStatus = 1; $this->rerunStatus = 1; } else { $this->rerunStatus = -2; } } catch (ExecutionFailureException | \RuntimeException $e) { $this->rerunStatus = -2; } } } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings