path['upgrades_dir'] = dirname(__DIR__).'/upgrade/list/'; $this->path['local_upgrades_dir'] = $cfg['Path']['web_root'].'upgrade/'; $this->verbose = $verbose; $this->useLocalUpgrades = $useLocalUpgrades; return true; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // prohleda slozku a najde vsechny dostupne upgrade public function loadFileNamesFromFolder($folder) { $files = []; $dp = @opendir($folder); if ($dp) { while ($fileThis = readdir($dp)) { if (!is_dir($fileThis)) { // pokud odpovida jmeno souboru tvaru upgradu if (preg_match('/^(.+)\\.php$/i', $fileThis, $matches)) { $files[] = [ 'file' => $folder.$fileThis, 'date' => $matches[1], ]; } } } } sort($files); return $files; } public function getUpgrades() { $shared_files = $this->loadFileNamesFromFolder($this->path['upgrades_dir']); if ($this->useLocalUpgrades) { $local_files = $this->loadFileNamesFromFolder($this->path['local_upgrades_dir']); $shared_files = array_merge($shared_files, $local_files); } try { $finder = ServiceContainer::getService(\KupShop\KupShopBundle\Util\System\BundleFinder::class); $files = $finder->getBundlesPath('Resources/upgrade/'); foreach ($files as $file) { if ($this->loadFileNamesFromFolder($file) != null) { $shared_files = array_merge($shared_files, $this->loadFileNamesFromFolder($file)); } } } catch (Throwable $e) { echo 'Failed loading bundle upgrades: '.$e->getMessage(); } return $shared_files; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function checkTableExists($tableName, $dbName = null) { $change = true; $db = $dbName ? 'IN '.$dbName : ''; $SQL = sqlQuery("SHOW TABLES {$db} LIKE '".getTableName($tableName, false)."'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkDatabaseExists($tableName) { $change = true; $SQL = sqlQuery("SHOW DATABASES LIKE '".getTableName($tableName, false)."'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkTableIsEmpty($tableName) { $change = true; if ($this->checkTableExists($tableName)) { return true; } $SQL = sqlQuery('SELECT * FROM '.getTableName($tableName, false)); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkColumnExists($table, $column) { $change = true; try { $SQL = sqlQuery('SHOW FIELDS FROM '.getTableName($table)); while (($row = sqlFetchArray($SQL)) !== false) { if ($row['Field'] == $column) { $change = false; break; } } } catch (Exception $e) { // pass } return $change; } public function checkColumnIsEmpty($tableName, $columnName) { $change = true; $SQL = sqlQuery('SELECT * FROM '.getTableName($tableName).' WHERE '.$columnName); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkHasPrimaryKey(string $table) { return !$this->checkConstraintExists($table, 'PRIMARY'); } public function checkConstraintExists($table, $name) { $change = true; // $SQL = sqlQuery("SELECT * // FROM information_schema.KEY_COLUMN_USAGE // WHERE CONSTRAINT_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND CONSTRAINT_NAME='$name' AND TABLE_NAME='".getTableName($table, false)."'"); $SQL = sqlQuery("SHOW KEYS FROM {$table} WHERE key_name='{$name}'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkIsInnoDB($tableName) { $change = true; $SQL = sqlFetchAssoc(sqlQuery("SHOW TABLE STATUS WHERE Name = '".getTableName($tableName, false)."'")); if ($SQL['Engine'] == 'InnoDB') { $change = false; } return $change; } public function checkFulltextConstraintExists($table, $name) { $change = true; $SQL = sqlQuery("SELECT 1 FROM information_schema.STATISTICS WHERE table_schema='".$GLOBALS['cfg']['Connection']['database']."' AND INDEX_NAME='{$name}' AND index_type='FULLTEXT' AND TABLE_NAME='".getTableName($table, false)."'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkConstraintTable($table, $name) { $change = true; $query = "SELECT 1 FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND CONSTRAINT_NAME='{$name}' AND TABLE_NAME='".getTableName($table, false)."'"; if (sqlNumRows(sqlQuery($query)) > 0) { $change = false; } return $change; } public function checkConstraintRule($table, $name, $delete = null, $update = null) { $change = true; $query = "SELECT 1 FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND CONSTRAINT_NAME='{$name}' AND TABLE_NAME='".getTableName($table, false)."'"; if ($delete) { $query .= "AND DELETE_RULE='{$delete}'"; } if ($update) { $query .= "AND UPDATE_RULE='{$update}'"; } if (sqlNumRows(sqlQuery($query)) > 0) { $change = false; } return $change; } public function checkConstraintWithColumnExists($table, $name, $column) { $change = true; $SQL = sqlQuery("SELECT 1 FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND CONSTRAINT_NAME='{$name}' AND TABLE_NAME='".getTableName($table, false)."' AND COLUMN_NAME='{$column}'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkForeignKeyExists($table, $column) { $change = true; $SQL = sqlQuery("SELECT 1 FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='{$GLOBALS['cfg']['Connection']['database']}' AND TABLE_NAME='".getTableName($table, false)."' AND COLUMN_NAME='{$column}' AND REFERENCED_COLUMN_NAME IS NOT NULL"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkIfTriggerExists($triggerName, $table = null) { $change = true; if (!$table) { $table = str_replace('trigger_', '', $triggerName); } $SQL = sqlQuery('SELECT 1 FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = :trigger AND EVENT_OBJECT_SCHEMA = DATABASE() AND EVENT_OBJECT_TABLE = :table', ['table' => $table, 'trigger' => $triggerName]); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkColumnType($table, $column, $type, $dbName = null) { $change = true; $type = sqlFormatInput($type); $dbName = $dbName ?? $GLOBALS['cfg']['Connection']['database']; $SQL = sqlQuery("SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='".$dbName."' AND TABLE_NAME='".getTableName($table, false)."' AND COLUMN_NAME='{$column}' AND COLUMN_TYPE COLLATE UTF8_GENERAL_CI LIKE '{$type}'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } /** * @deprecated Use self::checkEnumOptions */ public function checkEnumExists($table, $column, $value, &$values = '') { $change = true; $type = returnSqlResult("SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND TABLE_NAME='".getTableName($table, false)."' AND COLUMN_NAME='{$column}'"); if (empty($type)) { throw new \Exception("Can not find column {$table}:{$column}"); } if (strstr($type, "'{$value}'") !== false) { $change = false; } $values = $type; return $change; } public function checkEnumOptions(string $table, string $column, array $values) { $enums = ''; if (empty($values)) { throw new \Exception('No value'); } $this->checkEnumExists($table, $column, 'bžet', $enums); foreach ($values as $value) { if (strstr($enums, "'{$value}'") === false) { return true; } } return false; } public function updateEnumOptions($table, $column, $values, $default = '') { $enums = ''; $this->checkEnumExists($table, $column, 'bžet', $enums); foreach ($values as $value) { if (strstr($enums, "'{$value}'") === false) { $enums = str_replace(')', ",'{$value}')", $enums); } } if ($default) { $default = " DEFAULT '{$default}'"; } sqlQuery("ALTER TABLE {$table} CHANGE `{$column}` `{$column}` {$enums} NOT NULL {$default};"); } public function checkColumnIsNull($table, $column, $null) { $change = true; $SQL = sqlQuery("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND TABLE_NAME='".getTableName($table, false)."' AND COLUMN_NAME='{$column}' AND IS_NULLABLE='".($null ? 'YES' : 'NO')."'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkColumnCollation($table, $column, $collation) { $change = true; $SQL = sqlQuery("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='".$GLOBALS['cfg']['Connection']['database']."' AND TABLE_NAME='".getTableName($table, false)."' AND COLUMN_NAME='{$column}' AND COLLATION_NAME='{$collation}'"); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkColumnDefault($table, $column, $default) { $change = true; $SQL = sqlQuery("SHOW FIELDS FROM {$table} WHERE Field=:column AND `Default`=:default", ['default' => $default, 'column' => $column]); if (sqlNumRows($SQL) > 0) { $change = false; } return $change; } public function checkIndexNameExists($table, $name) { return sqlNumRows(sqlQuery("SHOW INDEXES FROM {$table} WHERE key_name=:name", ['name' => $name])) == 0; } public function checkIndexOnColumnExists($table, $column) { return sqlNumRows(sqlQuery("SHOW INDEXES FROM {$table} WHERE column_name=:column", ['column' => $column])) == 0; } public function checkModule($module, $submodule = null) { return findModule($module, $submodule); } public function checkDatabaseEncoding($collation) { return $collation != returnSQLResult('SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = :database', ['database' => $GLOBALS['cfg']['Connection']['database']]); } public function checkTableEncoding($table, $collation) { return $collation != returnSQLResult('SELECT CCSA.character_set_name FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA WHERE CCSA.collation_name = T.table_collation AND T.table_schema = :database AND T.table_name = :table', ['database' => $GLOBALS['cfg']['Connection']['database'], 'table' => $table]); } public function checkFunctionExists($name) { return sqlNumRows(sqlQuery('SHOW FUNCTION STATUS WHERE name=:name AND db=:db', ['name' => $name, 'db' => Config::get()['Connection']['database']])) === 0; } public function checkProcedureExists($name) { return sqlNumRows(sqlQuery('SHOW PROCEDURE STATUS WHERE name=:name AND db=:db', ['name' => $name, 'db' => Config::get()['Connection']['database']])) === 0; } public function getTableInformationSchema(string $tableName): array|false { $database = Config::get()['Connection']['database']; return sqlQueryBuilder()->select('*') ->from('information_schema.TABLES') ->andWhere(\Query\Operator::equals([ 'TABLE_SCHEMA' => $database, 'TABLE_NAME' => $tableName, ]))->execute()->fetchAssociative(); } // Posledni pouzita verze = 38 public function checkDataMigration($version) { return Settings::getDefault()->user_rights_version == $version - 1; } public function checkPrimaryKey($table, $column) { $return = sqlQueryBuilder()->select('COLUMN_KEY')->from('information_schema.COLUMNS') ->where(\Query\Operator::equals([ 'TABLE_SCHEMA' => $GLOBALS['cfg']['Connection']['database'], 'COLUMN_NAME' => $column, 'TABLE_NAME' => $table, 'COLUMN_KEY' => 'PRI', ])) ->execute()->fetchOne(); return $return === false; } public function commitDataMigration($version) { $settings = Settings::getDefault(); $settings->user_rights_version = $version; $settings->saveValue('user_rights_version', $version); \Settings::clearCache(); } public function loadIniFile($upgradeName) { // sestavit cestu k souboru $src = $this->path['upgrades_dir'].'/'.$upgradeName; if (!file_exists($src)) { return false; } $this->upgradeIni = @parse_ini_file($src, true); // kdyz neexistuje nastaveni souboru, pouzit defaukt if (!array_key_exists('file', $this->upgradeIni['UpgradeSettings'])) { $this->upgradeIni['UpgradeSettings']['file'] = str_replace('.ini', '', $upgradeName); } return $this->upgradeIni; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function includeUpgradeScript($upgradeName) { // sestavit cestu k souboru $src = $this->path['upgrades_dir'].'/'.$upgradeName; // ukoncit, kdy soubor neexistuje // ukoncit, kdy neni nalezeno nejdrive nastaveni if (!file_exists($src) || count($this->upgradeIni['UpgradeSettings']) == 0 ) { return false; } include_once $src; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function addError($msg) { $this->errors[] = $msg; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function addNote($msg) { $this->notes[] = $msg; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function noChanges() { $this->notes[] = 'Nebylo zapotrebi zmen'; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function upgradeOK() { echo ' + '.get_class($this).'::??????: OK'; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////////////////////// public function printResult($global = true) { if (count($this->errors) > 0) { echo 'Behem upgradu vznikly nasledujici chyby'; for ($i = 0; $i < count($this->errors); $i++) { echo '
  • '.$this->errors[$i].'
  • '; } $this->errors = []; } if (!$global && count($this->notes) > 0) { echo join(', ', $this->notes); $this->notes = []; } } public function run() { // Sorry za tohle, musím kvůli tomu že v buildu se spouští bez databáze ale musí proběhnout alespoň prvních pár migrací bez DB try { QueryHint::routeToMaster(); } catch (\Throwable) { // pass } // nacte seznam ini souboru $upgradesList = $this->getUpgrades(); // kontrola delky zpracovani skriptu controlTimeLimit(); ini_set('memory_limit', '1G'); if ($this->verbose) { echo '
    ';
            }
    
            $this->loadUpgrades($upgradesList);
    
            $this->orderUpgrades($upgradesList);
    
            // -----------------------------------------------------
            // projet a spustit vsechny nalezene upgrady
            foreach ($upgradesList as $index => $upgrade) {
                $className = get_class($upgrade['instance']);
                if ($this->verbose) {
                    if ($index > 0) {
                        echo '---------------------------------------------------------'."\n";
                    }
    
                    echo 'Upgrade #'.($index + 1)." - {$className} ({$upgrade['date']})\n";
                }
    
                // inicializuje tridu
                $time = microtime(true);
                /** @var Upgrade $upgrade */
                $upgrade = $upgrade['instance'];
                $upgrade->setVerbose($this->verbose);
                try {
                    $upgrade->upgrade();
                } catch (Exception $exception) {
                    echo "Upgrade error: {$className}\n";
                    throw $exception;
                }
                if ($this->verbose) {
                    echo ' => '.round((microtime(true) - $time) * 1000).'ms'."\n";
                }
    
                // kontrola delky zpracovani skriptu
                controlTimeLimit();
            }
    
            if ($this->verbose) {
                echo '
    '."\n"; } } public function setVerbose($verbose) { $this->verbose = $verbose; } public function upgrade() { throw new LogicException('class Upgrade: method upgrade() not defined'); } public function getShopDir() { global $cfg; return realpath($cfg['Path']['web_root']).'/'; } public function loadUpgrades(&$upgradesList) { foreach ($upgradesList as $index => &$upgrade) { // Load class if not already loaded if (!isset(static::$instanceCache[$upgrade['file']])) { // nacist upgrade - trik jak ziskat nactenou classu $classes = get_declared_classes(); include_once $upgrade['file']; $diff = array_diff(get_declared_classes(), $classes); $className = reset($diff); if (!class_exists($className)) { echo '!!! Chyba - chybi trida pro upgrade: '.$upgrade['file']; continue; } /* @var Upgrade $u */ static::$instanceCache[$upgrade['file']] = new $className(); } $u = static::$instanceCache[$upgrade['file']]; $upgrade['priority'] = $u->getPriority(); $upgrade['instance'] = $u; // Upgrades from egnine/upgrade/list has higher priority if ($upgrade['priority'] == null) { if (strpos($upgrade['file'], 'upgrade/list') !== false) { $upgrade['priority'] = -100; } else { $upgrade['priority'] = 0; } } } } public function orderUpgrades(&$upgradesList) { usort($upgradesList, ['Upgrade', 'prioritySort']); } // sort by priority and date public function prioritySort($a, $b) { $order = ['priority' => 'asc', 'date' => 'asc']; $t = [true => -1, false => 1]; $r = true; $k = 1; foreach ($order as $key => $value) { $k = ($value === 'asc') ? 1 : -1; $r = ($a[$key] < $b[$key]); if ($a[$key] !== $b[$key]) { return $t[$r] * $k; } } return $t[$r] * $k; } /** * @return int */ public function getPriority() { return $this->priority; } /** * @throws ReflectionException */ public function ensureDbExists(string $db): bool { try { sqlGetConnection()->connect(); sqlQuery("USE `{$db}`"); return false; } catch (Exception $e) { sqlGetConnection()->close(); } $reflectionClass = new ReflectionClass(\Doctrine\DBAL\Connection::class); try { $reflectionProperty = $reflectionClass->getProperty('_params'); } catch (ReflectionException $e) { $reflectionProperty = $reflectionClass->getProperty('params'); } $reflectionProperty->setAccessible(true); $params = $reflectionProperty->getValue(sqlGetConnection()); unset($params['dbname']); $reflectionProperty->setValue(sqlGetConnection(), $params); sqlGetConnection()->connect(); $params['dbname'] = $db; $reflectionProperty->setValue(sqlGetConnection(), $params); sqlQuery("CREATE DATABASE IF NOT EXISTS `{$db}`"); sqlQuery("USE `{$db}`"); return true; } protected function isAllowed() { return true; } protected function addPrivilegeToPrivilegedAdmins(?string $conditionPrivilege = null, string $newPrivilege) { $qb = sqlQueryBuilder()->select('id, privilege')->from('admins'); if (isset($conditionPrivilege)) { $qb->where('privilege LIKE :privilege') ->setParameter('privilege', '%'.$conditionPrivilege.'%'); } foreach ($qb->execute() as $user) { $privilegeArr = explode('|', $user['privilege']); if (isset($conditionPrivilege) && !in_array($conditionPrivilege, $privilegeArr)) { continue; } // pridavam pravo vsem krom adminum z pravem ALL_RIGHTS if (!isset($conditionPrivilege) && in_array('ALL_RIGHTS', $privilegeArr)) { continue; } $privilegeArr[] = $newPrivilege; $privilege = join('|', $privilegeArr); $this->updateSQL('admins', ['privilege' => $privilege], ['id' => $user['id']]); } } } class UpgradeNew extends Upgrade { public function upgrade() { $changed = false; $methods = get_class_methods($this); if (!$this->isAllowed()) { $this->addNote('Preskoceno'); $methods = []; } foreach ($methods as $method) { if (!preg_match('/^check(Rightfulness)?_(.+)$/i', $method, $matches)) { continue; } $name = $matches[2]; $checkRightfulness = $matches[0]; if (method_exists($this, "makeChanges_{$name}")) { $makeChanges = "makeChanges_{$name}"; } elseif (method_exists($this, "upgrade_{$name}")) { $makeChanges = "upgrade_{$name}"; } else { continue; } // jestlize je opravnene udelat upgrade, tak provest $time = microtime(true); $change = $this->$checkRightfulness(); if (is_null($change)) { echo 'Method "'.$checkRightfulness.'" in class "'.get_class($this).'" returns NULL instead of boolean! You might have missed the word "return".'.PHP_EOL; } if ($this->verbose && round((microtime(true) - $time) * 1000) > 10) { echo ' ! '.$name.': '.$this->getComment($makeChanges).' checking took '.round((microtime(true) - $time) * 1000).'ms)'."\n"; } if ($change) { $changed = true; $comment = $this->getComment($makeChanges) ?: get_class($this).'::'.$makeChanges; echo ' + '.$comment.': '; flush(); $time = microtime(true); $this->$makeChanges(); $this->printResult(false); echo ' ('.round((microtime(true) - $time) * 1000).'ms)'; echo "\n"; } } if (!$changed) { $this->noChanges(); $this->printResult(); } } public function getComment($method) { $c = new ReflectionClass($this); $m = $c->getMethod($method); $s = $m->getDocComment(); $s = str_replace('/*', '', $s); $s = str_replace('*/', '', $s); $s = str_replace('*', '', $s); return trim($s); } public function checkSetOptions($table, $column, $values) { $this->checkEnumExists($table, $column, 'bžet', $set); $set = preg_match_all("/'([^']+)'/", $set, $matches); if (array_diff($values, $matches[1])) { return true; } return false; } public function updateSetOptions($table, $column, $values) { $typesString = array_map(fn ($value) => "'{$value}'", $values); sqlQuery("ALTER TABLE {$table} MODIFY COLUMN {$column} SET(".join(',', $typesString).');'); } public function upgradeOK() { $this->notes[] = 'OK'; } }