Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F775516
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Feb 5, 7:42 AM
Size
15 KB
Mime Type
text/x-diff
Expires
Fri, Feb 7, 7:42 AM (3 h, 19 m)
Engine
blob
Format
Raw Data
Handle
558062
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.2.x/core/units/helpers/deployment_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/deployment_helper.php (revision 15119)
+++ branches/5.2.x/core/units/helpers/deployment_helper.php (revision 15120)
@@ -1,606 +1,607 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class DeploymentHelper extends kHelper {
/**
* How many symbols from sql should be shown
*/
const SQL_TRIM_LENGTH = 120;
/**
* Name of module, that is processed right now
*
* @var string
* @access private
*/
private $moduleName = '';
/**
* List of sqls, associated with each revision (from project_upgrades.sql file)
*
* @var Array
* @access private
*/
private $revisionSqls = Array ();
/**
* List of revision titles as user typed them (from project_upgrades.sql file)
* @var Array
*/
private $revisionTitles = Array ();
/**
* Revision dependencies
*
* @var Array
* @access private
*/
private $revisionDependencies = Array ();
/**
* Numbers of revisions, that were already applied
*
* @var Array
* @access private
*/
private $appliedRevisions = Array ();
/**
* Don't change database, but only check syntax of project_upgrades.sql file and mark all revisions discovered as applied
*
* @var bool
* @access private
*/
private $dryRun = false;
/**
* Remembers script invocation method
*
* @var bool
* @access public
*/
public $isCommandLine = false;
/**
* IP Address of script invoker
*
* @var string
*/
public $ip = '';
public function __construct()
{
parent::__construct();
set_time_limit(0);
ini_set('memory_limit', -1);
$this->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);
$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
sort($this->appliedRevisions);
$fields_hash = Array (
'AppliedDBRevisions' => implode(',', $this->appliedRevisions),
);
$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)
{
if ( !$this->isCommandLine ) {
echo '<pre style="font-size: 10pt; color: #BBB; background-color: black; border: 2px solid darkgreen; padding: 8px;">' . 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;
if ( !file_exists($this->getModuleFile('project_upgrades.sql')) ) {
continue;
}
$ret = $ret && $this->deploy($module_name);
}
if ( $ret && !$this->dryRun ) {
$this->resetCaches();
$this->refreshThemes();
}
if ( !$this->isCommandLine ) {
echo '</pre>' . PHP_EOL;
}
return $ret;
}
/**
* Deploys pending changes to a site
*
* @param string $module_name
* @return bool
* @access private
*/
private function deploy($module_name)
{
echo $this->colorText('Deploying Module "' . $module_name . '":', 'cyan', true) . PHP_EOL;
if ( !$this->upgradeDatabase() ) {
return false;
}
if ( !$this->dryRun ) {
$this->importLanguagePack();
}
echo $this->colorText('Done with Module "' . $module_name . '".', 'green', true) . PHP_EOL . PHP_EOL;
return true;
}
/**
* Import latest languagepack (without overwrite)
*
* @return void
* @access private
*/
private function importLanguagePack()
{
$language_import_helper =& $this->Application->recallObject('LanguageImportHelper');
/* @var $language_import_helper LanguageImportHelper */
echo 'Importing LanguagePack ... ';
$filename = $this->getModuleFile('english.lang');
$language_import_helper->performImport($filename, '|0|1|2|', $this->moduleName, LANG_SKIP_EXISTING);
$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);
$this->displayStatus('OK');
// 3. reset sections cache
echo 'Resetting Sections Cache ... ';
$admin_event = new kEvent('adm:OnResetSections');
$this->Application->HandleEvent($admin_event);
$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);
$this->displayStatus('OK');
}
/**
* Runs database upgrade script
*
* @return bool
* @access private
*/
private function upgradeDatabase()
{
$this->loadAppliedRevisions();
$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();
return $applied;
}
/**
* Collects database revisions from "project_upgrades.sql" file.
*
* @return bool
* @access private
*/
private function collectDatabaseRevisions()
{
$filename = $this->getModuleFile('project_upgrades.sql');
if ( !file_exists($filename) ) {
return true;
}
$sqls = file_get_contents($filename);
preg_match_all("/# r([\d]+)([^\:]*):.*?(\n|$)/s", $sqls, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
if ( !$matches ) {
$this->displayStatus('FAILED' . PHP_EOL . 'No Database Revisions Found');
return false;
}
foreach ($matches as $index => $match) {
$revision = $match[1][0];
if ( $this->revisionApplied($revision) ) {
// skip applied revisions
continue;
}
if ( isset($this->revisionSqls[$revision]) ) {
// duplicate revision among non-applied ones
$this->displayStatus('FAILED' . PHP_EOL . 'Duplicate revision #' . $revision . ' found');
return false;
}
// get revision sqls
$start_pos = $match[0][1] + strlen($match[0][0]);
$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 ) {
// 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 ) {
$this->revisionDependencies[$revision] = $revision_lependencies;
}
}
ksort($this->revisionSqls);
ksort($this->revisionDependencies);
return true;
}
/**
* Checks that all dependent revisions are either present now OR were applied before
*
* @return bool
* @access private
*/
private function checkRevisionDependencies()
{
foreach ($this->revisionDependencies as $revision => $revision_dependencies) {
foreach ($revision_dependencies as $revision_dependency) {
if ( $this->revisionApplied($revision_dependency) ) {
// revision dependend upon already applied -> depencency fulfilled
continue;
}
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]) ) {
$this->displayStatus('FAILED' . PHP_EOL . 'Revision #' . $revision . ' depends on missing revision #' . $revision_dependency . '!');
return false;
}
}
}
return true;
}
/**
* Runs all pending sqls
*
* @return bool
* @access private
*/
private function applyRevisions()
{
if ( !$this->revisionSqls ) {
return true;
}
if ( $this->dryRun ) {
$this->appliedRevisions = array_merge($this->appliedRevisions, array_keys($this->revisionSqls));
return true;
}
echo 'Upgrading Database ... ' . PHP_EOL;
foreach ($this->revisionSqls as $revision => $sqls) {
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", "# \\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 $sql) {
- $this->toLog($sql);
-
if ( substr($sql, 0, 1) == '#' ) {
// output comment as is
+ $this->toLog($sql);
echo $this->colorText($sql, 'purple') . PHP_EOL;
continue;
}
elseif ( $sql ) {
+ $this->toLog($sql . ' ... ', false);
echo mb_substr(trim(preg_replace('/(\n|\t| )+/is', ' ', $sql)), 0, self::SQL_TRIM_LENGTH) . ' ... ';
$this->Conn->Query($sql);
if ( $this->Conn->hasError() ) {
// consider revisions with errors applied
$this->appliedRevisions[] = $revision;
return false;
}
else {
+ $this->toLog('OK (' . $this->Conn->getAffectedRows() . ')');
$this->displayStatus('OK (' . $this->Conn->getAffectedRows() . ')');
}
}
}
$this->appliedRevisions[] = $revision;
}
echo PHP_EOL;
return true;
}
/**
* Error handler for sql errors
*
* @param int $code
* @param string $msg
* @param string $sql
* @return bool
* @access public
*/
public function handleSqlError($code, $msg, $sql)
{
- $this->toLog('SQL Error #' . $code . ': ' . $msg);
+ $this->toLog('FAILED' . PHP_EOL . '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;
}
/**
* Checks if given revision was already applied
*
* @param int $revision
* @return bool
* @access private
*/
private function revisionApplied($revision)
{
foreach ($this->appliedRevisions as $applied_revision) {
// revision range
$applied_revision = explode('-', $applied_revision, 2);
if ( !isset($applied_revision[1]) ) {
// convert single revision to revision range
$applied_revision[1] = $applied_revision[0];
}
if ( $revision >= $applied_revision[0] && $revision <= $applied_revision[1] ) {
return true;
}
}
return false;
}
/**
* Returns path to given file in current module install folder
*
* @param string $filename
* @return string
* @access private
*/
private function getModuleFile($filename)
{
$module_folder = $this->Application->findModule('Name', $this->moduleName, 'Path');
return FULL_PATH . DIRECTORY_SEPARATOR . $module_folder . 'install/' . $filename;
}
/**
* Extracts revisions from string in format "(1,3,5464,23342,3243)"
*
* @param string $string
* @return Array
* @access private
*/
private function parseRevisionDependencies($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 '<span style="color: ' . $html_color . '">' . $text . '</span>';
}
/**
* 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 '<strong>' . $text . '</strong>';
}
/**
* 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
Event Timeline
Log In to Comment