Source of file MigrationHelper.php
Size: 24,836 Bytes - Last Modified: 2020-10-24T02:46:31+00:00
/home/travis/build/NextDom/nextdom-core/src/Helpers/MigrationHelper.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 | <?php /* * This file is part of the NextDom software (https://github.com/NextDom or http://nextdom.github.io). * Copyright (c) 2018 NextDom. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ namespace NextDom\Helpers; use NextDom\Enums\DateFormat; use NextDom\Enums\FoldersReferential; use NextDom\Enums\LogTarget; use NextDom\Enums\PlanDisplayType; use NextDom\Exceptions\CoreException; use NextDom\Managers\BackupManager; use NextDom\Managers\ConfigManager; use NextDom\Managers\ConsistencyManager; use NextDom\Managers\CronManager; use NextDom\Managers\InteractDefManager; use NextDom\Managers\PlanHeaderManager; use NextDom\Managers\PlanManager; use NextDom\Managers\UpdateManager; use NextDom\Managers\UserManager; use NextDom\Model\Entity\Cron; use NextDom\Model\Entity\PlanHeader; /** * Class MigrationHelper * @package NextDom\Helpers */ class MigrationHelper { /** * Log message * * @param $targetLogFile * @param $message * @throws \Exception */ private static function logMessage($targetLogFile, $message) { if ($targetLogFile == LogTarget::MIGRATION) { LogHelper::addInfo($targetLogFile, $message, ''); } else { ConsoleHelper::process($message); } } /** * Main migrate process * @param string $logFile log name file to display information * @throws \Exception */ public static function migrate($logFile = LogTarget::MIGRATION) { $migrate = false; if ($logFile == LogTarget::MIGRATION) { LogHelper::clear($logFile); } //get current version $currentVersion = explode('.', NextDomHelper::getNextdomVersion()); $currentVersion = array_map('intval', $currentVersion); // get previous version if (ConfigManager::byKey('lastUpdateVersion') == null) { $migrate = true; $previousVersion = [0, 0, 0]; } else { $previousVersion = explode('.', ConfigManager::byKey('lastUpdateVersion')); //compare versions if ($currentVersion !== null && $previousVersion !== null) { $migrate = self::compareDigit(count($currentVersion), count($previousVersion), $previousVersion, $currentVersion, 0); } } $previousVersion = array_map('intval', $previousVersion); $message = 'Migration/Update process from --> ' . implode('.', $previousVersion); self::logMessage($logFile, $message); $message = 'Migration/Update process to --> ' . implode('.', $currentVersion); self::logMessage($logFile, $message); // call migrate functions if ($migrate === true) { $previousVersion[2]++; while ($previousVersion[0] <= $currentVersion[0]) { while ($previousVersion[1] <= 10) { while ($previousVersion[2] <= 10) { if (method_exists(get_class(), 'migrate_' . $previousVersion[0] . '_' . $previousVersion[1] . '_' . $previousVersion[2])) { $migrateMethod = 'migrate_' . $previousVersion[0] . '_' . $previousVersion[1] . '_' . $previousVersion[2]; $message = 'Start migration process for ' . $migrateMethod; self::logMessage($logFile, $message); try { self::$migrateMethod($logFile); } catch (\Exception $exception) { throw new CoreException(); } $message = 'Done migration process for ' . $migrateMethod; self::logMessage($logFile, $message); ConfigManager::save('lastUpdateVersion', $previousVersion[0] . '.' . $previousVersion[1] . '.' . $previousVersion[2], 'core'); $message = 'Save migration process for ' . $migrateMethod; self::logMessage($logFile, $message); } $previousVersion[2] += 1; } $previousVersion[2] = 0; $previousVersion[1] += 1; } $previousVersion[1] = 0; $previousVersion[0] += 1; } } self::fixHtaccess(); self::replaceJeedomInDatabase($logFile); ConfigManager::save('lastUpdateVersion', $currentVersion[0] . '.' . $currentVersion[1] . '.' . $currentVersion[2], 'core'); } /** * Compare digit version * @param int $currentVersionSize * @param int $previousVersionSize * @param array $previousVersion * @param array $currentVersion * @param int $index * @return bool */ private static function compareDigit(int $currentVersionSize, int $previousVersionSize, array $previousVersion, array $currentVersion, int $index): bool { $migrate = false; if ($index > 3) { return $migrate; } if ($currentVersionSize > $index && $previousVersionSize > $index) { if ($previousVersion[$index] < $currentVersion[$index]) { $migrate = true; } else { $migrate = self::compareDigit($currentVersionSize, $previousVersionSize, $previousVersion, $currentVersion, $index + 1); } } return $migrate; } /** * Fix htaccess problems in data folder * * @param string $logFile */ private static function fixHtaccess($logFile = LogTarget::MIGRATION) { exec("find " . NEXTDOM_DATA . " -name '.htaccess' -exec sed -i s/\\|jpeg\\|/\\|jpe\\?g\\|/g {} +"); } /** * Replace jeedom to nextdom in database * * @param string $logFile log name file to display information * * @throws \Exception */ private static function replaceJeedomInDatabase($logFile = LogTarget::MIGRATION) { self::logMessage($logFile, 'Replace jeedom in database'); //Update Config table $allConfigKeys = ConfigManager::searchKey('', 'core'); foreach ($allConfigKeys as $keyData) { if (strpos($keyData['key'], 'jeedom') !== false) { $configValue = ConfigManager::byKey($keyData['key'], 'core'); ConfigManager::save(str_replace('jeedom', 'nextdom', $keyData['key']), $configValue, 'core'); ConfigManager::remove($keyData['key'], 'core'); } } ConfigManager::save('nextdom::firstUse', 0, 'core'); // Update Crons table $sql = 'UPDATE `cron` SET `class` = "nextdom" WHERE `class` = "jeedom"'; try { DBHelper::exec($sql); } catch (\Exception $e) { } // Check doublon on update foreach (ConsistencyManager::getDefaultCrons() as $cronClass => $cronData) { foreach ($cronData as $cronName => $cronConfig) { $sql = 'SELECT ' . DBHelper::buildField(CronManager::CLASS_NAME) . ' FROM ' . CronManager::DB_CLASS_NAME . ' WHERE `class` = :class AND `function` = :function'; $params = [ 'class' => $cronClass, 'function' => $cronName, ]; /** @var Cron[] $result */ $result = DBHelper::getAllObjects($sql, $params, CronManager::CLASS_NAME); if (count($result) > 1) { $result[1]->remove(); } } } // Update jeedom version foreach (UpdateManager::all() as $update) { if ($update->getType() == 'core' && $update->getName() == 'jeedom' && $update->getLogicalId() == 'jeedom') { $update->setName('nextdom'); $update->setLogicalId('nextdom'); $update->save(); } } } /***************************************************************** 0.0.0 Migration process *****************************************************************/ /** * 0.0.0 Migration process * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_0_0_0($logFile = LogTarget::MIGRATION) { $migrateFile = sprintf("%s/install/migrate/migrate_0_0_0.sql", NEXTDOM_ROOT); BackupManager::loadSQLFromFile($migrateFile); self::logMessage($logFile, 'Database basic update'); foreach (InteractDefManager::all() as $interactDef) { $interactDef->setEnable(1); $interactDef->save(); } self::logMessage($logFile, 'Interact definition update'); } /***********************************************************************************************************************************************************/ /***************************************************************** 0.7.1 Migration process *****************************************************************/ /** * Migration to pass during migrate_themes_to_data (should be done in 0.3.0) * @param string $logFile log name file to display information * @throws \Exception */ private static function movePersonalFoldersAndFilesToData($logFile = LogTarget::MIGRATION) { self::logMessage($logFile, 'Update theme folder'); FileSystemHelper::mkdirIfNotExists(NEXTDOM_DATA . '/data/custom/', 0775, true); $dir = new \RecursiveDirectoryIterator(NEXTDOM_ROOT, \FilesystemIterator::SKIP_DOTS); // Flatten the recursive iterator, folders come before their files $it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST); // Maximum depth is 1 level deeper than the base folder $it->setMaxDepth(0); try { self::logMessage($logFile, 'Start moving files and folders process to ' . NEXTDOM_DATA); // Basic loop displaying different messages based on file or folder foreach ($it as $fileInfo) { if ($fileInfo->isDir() || $fileInfo->isFile()) { if (!in_array($fileInfo->getFilename(), FoldersReferential::NEXTDOMFOLDERS) && !in_array($fileInfo->getFilename(), FoldersReferential::NEXTDOMFILES) && !in_array($fileInfo->getFilename(), FoldersReferential::JEEDOMFOLDERS) && !in_array($fileInfo->getFilename(), FoldersReferential::JEEDOMFILES) && !is_link($fileInfo->getFilename())) { $fileToReplace = $fileInfo->getFilename(); self::logMessage($logFile, 'Moving ' . NEXTDOM_ROOT . '/' . $fileToReplace); FileSystemHelper::mv(NEXTDOM_ROOT . '/' . $fileToReplace, sprintf("%s/%s", NEXTDOM_DATA . '/data/custom/', $fileToReplace)); self::migratePlanPath($logFile, $fileToReplace, '', 'data/custom/'); } } } } catch (\Exception $exception) { throw(new CoreException()); } try { $dir = new \RecursiveDirectoryIterator(NEXTDOM_DATA . '/data/custom/', \FilesystemIterator::SKIP_DOTS); // Flatten the recursive iterator, folders come before their files $it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST); // Maximum depth is 1 level deeper than the base folder $it->setMaxDepth(0); // Basic loop displaying different messages based on file or folder foreach ($it as $fileInfo) { if (!is_link($fileInfo->getFilename())) { $fileToReplace = $fileInfo->getFilename(); self::migratePlanPath($logFile, $fileToReplace, '', 'data/custom/'); } } } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } try { $dir = new \RecursiveDirectoryIterator(NEXTDOM_ROOT . '/public/img/', \FilesystemIterator::SKIP_DOTS); // Flatten the recursive iterator, folders come before their files $it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST); // Maximum depth is 1 level deeper than the base folder $it->setMaxDepth(0); // Basic loop displaying different messages based on file or folder foreach ($it as $fileInfo) { if (!is_link($fileInfo->getFilename()) && Utils::startsWith($fileInfo->getFilename(), 'plan')) { $fileToReplace = $fileInfo->getFilename(); self::migratePlanPath($logFile, $fileToReplace, 'public/img/', 'data/plan/'); self::migratePlanPath($logFile, $fileToReplace, 'core/img/', 'data/plan/'); } } } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } try { if (file_exists(NEXTDOM_DATA . '/data/custom/plans')) { $dir = new \RecursiveDirectoryIterator(NEXTDOM_DATA . '/data/custom/plans', \FilesystemIterator::SKIP_DOTS); // Flatten the recursive iterator, folders come before their files $it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST); // Maximum depth is 1 level deeper than the base folder $it->setMaxDepth(0); // Basic loop displaying different messages based on file or folder foreach ($it as $fileInfo) { if (!is_link($fileInfo->getFilename()) && Utils::startsWith($fileInfo->getFilename(), 'plan_')) { $fileToReplace = $fileInfo->getFilename(); self::migratePlanPath($logFile, $fileToReplace, 'public/img/', 'data/plan/'); self::migratePlanPath($logFile, $fileToReplace, 'core/img/', 'data/plan/'); self::migratePlanPath($logFile, $fileToReplace, 'data/custom/plans/', 'data/plan/'); self::logMessage($logFile, 'File' . NEXTDOM_DATA . '/data/custom/plans/' . $fileInfo->getFilename()); $dirname = dirname(NEXTDOM_DATA . '/data/plan/' . $fileInfo->getFilename()); if (!is_dir($dirname)) { mkdir($dirname, 0775, true); } FileSystemHelper::rcopy(NEXTDOM_DATA . '/data/custom/plans/' . $fileInfo->getFilename(), NEXTDOM_DATA . '/data/plan/' . $fileInfo->getFilename()); } } self::migratePlanPath($logFile, '', 'public/img/', 'data/plan/'); self::migratePlanPath($logFile, '', 'core/img/', 'data/plan/'); } } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } self::logMessage($logFile, 'Migrate theme to data folder is done'); } /** * Update plan table to change path given in parameter * @param string $logFile * @param string $fileToReplace * @param string $oldReferencePath * @param string $newReferencePath * @throws CoreException * @throws \ReflectionException * @throws \NextDom\Exceptions\OperatingSystemException */ private static function migratePlanPath($logFile, $fileToReplace, $oldReferencePath, $newReferencePath) { if (empty($oldReferencePath)) { $oldReferencePath = ''; } self::logMessage($logFile, 'Migrate ' . $oldReferencePath . $fileToReplace . ' to ' . $newReferencePath . $fileToReplace); foreach (PlanManager::all() as $plan) { foreach (PlanDisplayType::getValues() as $displayType) { $html = $plan->getDisplay($displayType); if ($html !== null) { if ($displayType == PlanDisplayType::PATH && !empty($oldReferencePath)) { $html = str_replace($oldReferencePath . $fileToReplace, $newReferencePath . $fileToReplace, $html); } else { $html = str_replace('"' . $oldReferencePath . $fileToReplace, '"' . $newReferencePath . $fileToReplace, $html); } $plan->setDisplay($displayType, $html); $plan->save(); } } } } /** * Migration removing user.function.class.php (should be done in 0.5.2) * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_removeFunctionClass($logFile = LogTarget::MIGRATION) { if (is_dir(NEXTDOM_DATA . '/data/php')) { FileSystemHelper::rrmfile(NEXTDOM_DATA . '/data/php/user.function.class.php'); FileSystemHelper::rrmfile(NEXTDOM_DATA . '/data/php/user.function.class.sample.php'); } self::logMessage($logFile, 'user.function files removed'); } /** * Migration modifying swapiness (should be done in 0.6.1) * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_swapiness($logFile = LogTarget::MIGRATION) { try { exec("sudo sed -i '/vm.swappiness=/d' /etc/sysctl.d/99-sysctl.conf"); exec("sudo echo 'vm.swappiness=10' >> /etc/sysctl.d/99-sysctl.conf"); } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } self::logMessage($logFile, 'Swapiness configuration update'); } /** * Migration adding icon field to message table (should be done in 0.6.2) * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_updateHash($logFile = LogTarget::MIGRATION) { foreach (UserManager::all() as $user) { if($user->getProfils() != 'admin' || $user->getOptions('doNotRotateHash',0) == 1){ continue; } $user->setHash(''); $user->getHash(); $user->setOptions('hashGenerated',date(DateFormat::FULL)); $user->save(); } $sql = "SELECT concat('ALTER TABLE ', TABLE_NAME, ' DROP FOREIGN KEY ', CONSTRAINT_NAME, ';') FROM information_schema.key_column_usage WHERE CONSTRAINT_SCHEMA = 'nextdom' AND REFERENCED_TABLE_NAME IS NOT NULL;"; $result = DBHelper::getAll($sql); foreach ($result as $value) { try { DBHelper::exec(array_values($value)[0]); } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } } } /** * Migration adding icon field to message table (should be done in 0.7.0) * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_messageToAddIcon($logFile = LogTarget::MIGRATION) { try { DBHelper::exec("ALTER message ADD icon MEDIUMTEXT"); } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } } /** * Migration changing engine of interactQuery table (should be done in 0.7.1) * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_interactQueryEngine($logFile = LogTarget::MIGRATION) { try { DBHelper::exec("ALTER TABLE `interactQuery` ENGINE=InnoDB;"); //Peut-être fait plus tot. } catch (\Exception $exception) { self::logMessage($logFile, $exception.getMessage()); throw(new CoreException()); } } /** * 0.7.1 Migration process (apply all migration process from 0.3.0 not done yet) * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_0_7_1($logFile = LogTarget::MIGRATION) { self::movePersonalFoldersAndFilesToData($logFile); self::migrate_removeFunctionClass($logFile); self::migrate_swapiness($logFile); self::migrate_updateHash($logFile); self::migrate_messageToAddIcon($logFile); self::migrate_interactQueryEngine($logFile); } /***************************************************************** 0.8.0 Migration process *****************************************************************/ /** * 0.8.0 Migration process * @param string $logFile log name file to display information * @throws \Exception */ private static function migrate_0_8_0($logFile = LogTarget::MIGRATION) { DBHelper::exec("ALTER TABLE `cmd` add `html` mediumtext COLLATE utf8mb4_unicode_ci;"); DBHelper::exec("ALTER TABLE `eqLogic` DROP `eqReal_id`;"); DBHelper::exec("DROP TABLE `eqReal`;"); DBHelper::exec("ALTER TABLE planHeader ADD `order` int(11) DEFAULT NULL;"); DBHelper::exec("ALTER TABLE planHeader ADD KEY `order` (`order`)"); DBHelper::exec("DROP TABLE `plan3d`"); DBHelper::exec("DROP TABLE `plan3dHeader`"); $dir = NEXTDOM_DATA . '/'; $planHeaderList = PlanHeaderManager::all(); foreach ($planHeaderList as $planHeader) { if (!is_file($dir.$planHeader->getImgLink()) && !empty($planHeader->getImage('data'))) { $dirname = dirname($dir . $planHeader->getImgLink()); if (!is_dir($dirname)) { mkdir($dirname, 0775, true); } self::logMessage($logFile, "Create " . $planHeader->getImgLink()); file_put_contents($dir . $planHeader->getImgLink(), base64_decode($planHeader->getImage('data'))); $planHeader->setImage('data', ''); $planHeader->save(); } } self::logMessage($logFile, 'Create background plan, and delete data in DB.'); // delete /data/custom/plans/ $custom_plans = $dir . 'data/custom/plans/'; FileSystemHelper::rrmdir($custom_plans); DBHelper::exec("ALTER TABLE `type` DROP COLUMN `scenario`;"); DBHelper::exec("RENAME TABLE `widgets` TO `widget`"); $createWidget = "CREATE TABLE IF NOT EXISTS `widget` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(191) COLLATE utf8mb4_unicode_ci NOT NULL, `type` VARCHAR(27) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `subtype` VARCHAR(27) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `template` VARCHAR(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `display` text COLLATE utf8mb4_unicode_ci, `replace` text COLLATE utf8mb4_unicode_ci, `test` text COLLATE utf8mb4_unicode_ci, PRIMARY KEY (`id`), UNIQUE KEY `unique` (`type`,`subtype`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; DBHelper::exec($createWidget); global $CONFIG; self::logMessage($logFile, 'Migrate database and tables to utf8mb4.'); DBHelper::exec("ALTER DATABASE `'". $CONFIG['db']['dbname'] ."'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); $tables = DBHelper::getAllNextDomTables(); foreach ($tables as $table) { DBHelper::exec("ALTER TABLE `".$table['Tables_in_' . $CONFIG['db']['dbname']] . "` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"); } } } |