Index: branches/5.2.x/core/units/helpers/deployment_helper.php =================================================================== diff -u -N -r14699 -r15112 --- branches/5.2.x/core/units/helpers/deployment_helper.php (.../deployment_helper.php) (revision 14699) +++ branches/5.2.x/core/units/helpers/deployment_helper.php (.../deployment_helper.php) (revision 15112) @@ -1,6 +1,6 @@ isCommandLine = isset($GLOBALS['argv']) && count($GLOBALS['argv']); + + if ( !$this->isCommandLine ) { + $this->ip = $_SERVER['REMOTE_ADDR']; + } + elseif ( isset($GLOBALS['argv'][3]) ) { + $this->ip = $GLOBALS['argv'][3]; + } } + /** + * Adds message to script execution log + * + * @param string $message + * @param bool $new_line + * @return void + * @access private + */ + private function toLog($message, $new_line = true) + { + $log_file = (defined('RESTRICTED') ? RESTRICTED : WRITEABLE) . '/project_upgrades.log'; + + $fp = fopen($log_file, 'a'); + fwrite($fp, $message . ($new_line ? "\n" : '')); + fclose($fp); + + chmod($log_file, 0666); + } + + /** + * Loads already applied revisions list of current module + * + * @return void + * @access private + */ private function loadAppliedRevisions() { $sql = 'SELECT AppliedDBRevisions FROM ' . TABLE_PREFIX . 'Modules - WHERE Name = ' . $this->Conn->qstr( $this->moduleName ); + WHERE Name = ' . $this->Conn->qstr($this->moduleName); $revisions = $this->Conn->GetOne($sql); $this->appliedRevisions = $revisions ? explode(',', $revisions) : Array (); } + /** + * Saves applied revision numbers to current module record + * + * @return void + * @access private + */ private function saveAppliedRevisions() { // maybe optimize @@ -58,13 +152,24 @@ $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Modules', '`Name` = ' . $this->Conn->qstr($this->moduleName)); } + /** + * Deploys changes from all installed modules + * + * @param bool $dry_run + * @return bool + * @access public + */ public function deployAll($dry_run = false) { - $this->dryRun = $dry_run; - $this->lineEnding = $dry_run ? '
' . PHP_EOL : PHP_EOL; + if ( !$this->isCommandLine ) { + echo '
' . PHP_EOL;
+		}
 
 		$ret = true;
+		$this->dryRun = $dry_run;
 
+		$this->toLog(PHP_EOL . '[' . adodb_date('Y-m-d H:i:s') . '] === ' . $this->ip . ' ===');
+
 		foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 			$this->moduleName = $module_name;
 
@@ -75,11 +180,15 @@
 			$ret = $ret && $this->deploy($module_name);
 		}
 
-		if (!$this->dryRun) {
+		if ( $ret && !$this->dryRun ) {
 			$this->resetCaches();
 			$this->refreshThemes();
 		}
 
+		if ( !$this->isCommandLine ) {
+			echo '
' . PHP_EOL; + } + return $ret; } @@ -92,25 +201,26 @@ */ private function deploy($module_name) { - echo 'Deploying Module "' . $module_name . '":' . $this->lineEnding; + echo $this->colorText('Deploying Module "' . $module_name . '":', 'cyan', true) . PHP_EOL; - echo 'Upgrading Database ... '; if ( !$this->upgradeDatabase() ) { return false; } - echo 'OK' . $this->lineEnding; + if ( !$this->dryRun ) { + $this->importLanguagePack(); + } - $this->importLanguagePack(); + echo $this->colorText('Done with Module "' . $module_name . '".', 'green', true) . PHP_EOL . PHP_EOL; - echo 'Done.' . $this->lineEnding; - return true; } /** * Import latest languagepack (without overwrite) * + * @return void + * @access private */ private function importLanguagePack() { @@ -120,54 +230,63 @@ echo 'Importing LanguagePack ... '; $filename = $this->getModuleFile('english.lang'); $language_import_helper->performImport($filename, '|0|1|2|', $this->moduleName, LANG_SKIP_EXISTING); - echo 'OK' . $this->lineEnding; + $this->displayStatus('OK'); } /** * Resets unit and section cache * + * @return void + * @access private */ private function resetCaches() { // 2. reset unit config cache (so new classes get auto-registered) echo 'Resetting Unit Config Cache ... '; $admin_event = new kEvent('adm:OnResetConfigsCache'); $this->Application->HandleEvent($admin_event); - echo 'OK' . $this->lineEnding; + $this->displayStatus('OK'); // 3. reset sections cache echo 'Resetting Sections Cache ... '; $admin_event = new kEvent('adm:OnResetSections'); $this->Application->HandleEvent($admin_event); - echo 'OK' . $this->lineEnding; + $this->displayStatus('OK'); } /** * Rebuild theme files * + * @return void + * @access private */ private function refreshThemes() { echo 'Rebuilding Theme Files ... '; $admin_event = new kEvent('adm:OnRebuildThemes'); $this->Application->HandleEvent($admin_event); - echo 'OK' . $this->lineEnding; + $this->displayStatus('OK'); } /** * Runs database upgrade script * * @return bool + * @access private */ private function upgradeDatabase() { $this->loadAppliedRevisions(); - $this->Conn->errorHandler = Array(&$this, 'handleSqlError'); + $this->Conn->errorHandler = Array (&$this, 'handleSqlError'); + echo 'Verifying Database Revisions ... '; + if ( !$this->collectDatabaseRevisions() || !$this->checkRevisionDependencies() ) { return false; } + $this->displayStatus('OK'); + $applied = $this->applyRevisions(); $this->saveAppliedRevisions(); @@ -178,6 +297,7 @@ * Collects database revisions from "project_upgrades.sql" file. * * @return bool + * @access private */ private function collectDatabaseRevisions() { @@ -190,8 +310,8 @@ $sqls = file_get_contents($filename); preg_match_all("/# r([\d]+)([^\:]*):.*?(\n|$)/s", $sqls, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); - if (!$matches) { - echo 'No Database Revisions Found' . $this->lineEnding; + if ( !$matches ) { + $this->displayStatus('FAILED' . PHP_EOL . 'No Database Revisions Found'); return false; } @@ -206,7 +326,7 @@ if ( isset($this->revisionSqls[$revision]) ) { // duplicate revision among non-applied ones - echo 'Duplicate revision ' . $revision . ' found' . $this->lineEnding; + $this->displayStatus('FAILED' . PHP_EOL . 'Duplicate revision #' . $revision . ' found'); return false; } @@ -216,15 +336,16 @@ $end_pos = isset($matches[$index + 1]) ? $matches[$index + 1][0][1] : strlen($sqls); $revision_sqls = substr($sqls, $start_pos, $end_pos - $start_pos); - if (!$revision_sqls) { + if ( !$revision_sqls ) { // resision without sqls continue; } + $this->revisionTitles[$revision] = trim($match[0][0]); $this->revisionSqls[$revision] = $revision_sqls; $revision_lependencies = $this->parseRevisionDependencies($match[2][0]); - if ($revision_lependencies) { + if ( $revision_lependencies ) { $this->revisionDependencies[$revision] = $revision_lependencies; } } @@ -239,6 +360,7 @@ * Checks that all dependent revisions are either present now OR were applied before * * @return bool + * @access private */ private function checkRevisionDependencies() { @@ -249,14 +371,14 @@ continue; } - if ($revision_dependency >= $revision) { - echo 'Revision ' . $revision . ' has incorrect dependency to revision ' . $revision_dependency . '. Only dependencies to older revisions are allowed!' . $this->lineEnding; + if ( $revision_dependency >= $revision ) { + $this->displayStatus('FAILED' . PHP_EOL . 'Revision #' . $revision . ' has incorrect dependency to revision #' . $revision_dependency . '. Only dependencies to older revisions are allowed!'); return false; } if ( !isset($this->revisionSqls[$revision_dependency]) ) { - echo 'Revision ' . $revision . ' depends on missing revision ' . $revision_dependency . '!' . $this->lineEnding; + $this->displayStatus('FAILED' . PHP_EOL . 'Revision #' . $revision . ' depends on missing revision #' . $revision_dependency . '!'); return false; } @@ -270,49 +392,61 @@ * Runs all pending sqls * * @return bool + * @access private */ private function applyRevisions() { - if (!$this->revisionSqls) { + if ( !$this->revisionSqls ) { return true; } - if ($this->dryRun) { + if ( $this->dryRun ) { $this->appliedRevisions = array_merge($this->appliedRevisions, array_keys($this->revisionSqls)); return true; } - echo $this->lineEnding; + echo 'Upgrading Database ... ' . PHP_EOL; foreach ($this->revisionSqls as $revision => $sqls) { - echo 'Processing DB Revision: #' . $revision . ' ... '; + echo PHP_EOL . $this->colorText($this->revisionTitles[$revision], 'gray', true) . PHP_EOL; // 'Processing DB Revision: #' . $revision . ' ... '; - $sqls = str_replace("\r\n", "\n", $sqls); // convert to linux line endings - $no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines + $sqls = str_replace("\r\n", "\n", $sqls); // convert to linux line endings + $no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", "# \\1;\n", $sqls); // add ";" to each comment end to ensure correct split $sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); - foreach ($sqls as $index => $sql) { - if (!$sql || (substr($sql, 0, 1) == '#')) { - continue; // usually last line + foreach ($sqls as $sql) { + $this->toLog($sql); + + if ( substr($sql, 0, 1) == '#' ) { + // output comment as is + echo $this->colorText($sql, 'purple') . PHP_EOL; + continue; } + elseif ( $sql ) { + echo mb_substr(trim(preg_replace('/(\n|\t| )+/is', ' ', $sql)), 0, self::SQL_TRIM_LENGTH) . ' ... '; - $this->Conn->Query($sql); + $this->Conn->Query($sql); - if ( $this->Conn->hasError() ) { - // consider revisions with errors applied - $this->appliedRevisions[] = $revision; + if ( $this->Conn->hasError() ) { + // consider revisions with errors applied + $this->appliedRevisions[] = $revision; - return false; - } + return false; + } + else { + $this->displayStatus('OK'); + } + } } $this->appliedRevisions[] = $revision; - echo 'OK' . $this->lineEnding; } + echo PHP_EOL; + return true; } @@ -323,12 +457,15 @@ * @param string $msg * @param string $sql * @return bool + * @access public */ public function handleSqlError($code, $msg, $sql) { - echo 'Error (#' . $code . ': ' . $msg . ') during SQL processing:' . $this->lineEnding . $sql . $this->lineEnding; - echo 'Please execute rest of sqls in this revision by hand and run deployment script again.' . $this->lineEnding; + $this->toLog('SQL Error #' . $code . ': ' . $msg); + $this->displayStatus('FAILED' . PHP_EOL . 'SQL Error #' . $code . ': ' . $msg); + echo 'Please execute rest of SQLs in this Revision by hand and run deployment script again.' . PHP_EOL; + return true; } @@ -337,6 +474,7 @@ * * @param int $revision * @return bool + * @access private */ private function revisionApplied($revision) { @@ -362,6 +500,7 @@ * * @param string $filename * @return string + * @access private */ private function getModuleFile($filename) { @@ -375,15 +514,93 @@ * * @param string $string * @return Array + * @access private */ private function parseRevisionDependencies($string) { - if (!$string) { + if ( !$string ) { return Array (); } $string = explode(',', substr($string, 1, -1)); return array_map('trim', $string); } + + /** + * Applies requested color and bold attributes to given text string + * + * @param string $text + * @param string $color + * @param bool $bold + * @return string + * @access private + */ + private function colorText($text, $color, $bold = false) + { + if ( $this->isCommandLine ) { + $color_map = Array ( + 'black' => 30, // dark gray (in bold) + 'blue' => 34, // light blue (in bold) + 'green' => 32, // light green (in bold) + 'cyan' => 36, // light cyan (in bold) + 'red' => 31, // light red (in bold) + 'purple' => 35, // light purple (in bold) + 'brown' => 33, // yellow (in bold) + 'gray' => 37, // white (in bold) + ); + + return "\033[" . ($bold ? 1 : 0) . ";" . $color_map[$color] . "m" . $text . "\033[0m"; + } + + $html_color_map = Array ( + 'black' => Array ('normal' => '#000000', 'bold' => '#666666'), + 'blue' => Array ('normal' => '#00009C', 'bold' => '#3C3CFF'), + 'green' => Array ('normal' => '#009000', 'bold' => '#00FF00'), + 'cyan' => Array ('normal' => '#009C9C', 'bold' => '#00FFFF'), + 'red' => Array ('normal' => '#9C0000', 'bold' => '#FF0000'), + 'purple' => Array ('normal' => '#900090', 'bold' => '#F99CF9'), + 'brown' => Array ('normal' => '#C9C909', 'bold' => '#FFFF00'), + 'gray' => Array ('normal' => '#909090', 'bold' => '#FFFFFF'), + ); + + $html_color = $html_color_map[$color][$bold ? 'bold' : 'normal']; + + return '' . $text . ''; + } + + /** + * Makes given text bold + * + * @param string $text + * @return string + * @access private + */ + private function boldText($text) + { + if ( $this->isCommandLine ) { + return "\033[1m" . $text . "\033[0m"; + } + + return '' . $text . ''; + } + + /** + * Displays last command execution status + * + * @param string $status_text + * @param bool $new_line + * @return void + * @access private + */ + private function displayStatus($status_text, $new_line = true) + { + $color = substr($status_text, 0, 2) == 'OK' ? 'green' : 'red'; + + echo $this->colorText($status_text, $color, false); + + if ( $new_line ) { + echo PHP_EOL; + } + } } \ No newline at end of file