N°2249 - Supportability - Updater module (application upgrade)

This commit is contained in:
Eric
2019-12-13 17:28:35 +01:00
parent 2741a446ea
commit dd5ac38dd4
36 changed files with 3158 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
# itop-core-update
Application upgrade
This feature allows administrators to upgrade the application from a downloaded package.
namespace Combodo\iTop\CoreUpdate;

View File

@@ -0,0 +1,21 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\CoreUpdate;
use Combodo\iTop\CoreUpdate\Controller\AjaxController;
use ContextTag;
require_once(APPROOT.'application/startup.inc.php');
new ContextTag('Setup');
$oUpdateController = new AjaxController();
$oUpdateController->DisableInDemoMode();
$oUpdateController->AllowOnlyAdmin();
// Allow parallel execution of ajax requests
session_write_close();
$oUpdateController->HandleOperation();

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
<menus>
<menu id="iTopUpdate" xsi:type="WebPageMenuNode" _delta="define">
<rank>60</rank>
<parent>System</parent>
<url>$pages/exec.php?exec_module=itop-core-update&amp;exec_page=index.php&amp;c[menu]=iTopUpdate&amp;maintenance=true</url>
<enable_admin_only>1</enable_admin_only>
</menu>
</menus>
</itop_design>

View File

@@ -0,0 +1,108 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('EN US', 'English', 'English', array(
'iTopUpdate:UI:PageTitle' => 'Application Upgrade',
'itop-core-update:UI:SelectUpdateFile' => 'Application Upgrade',
'itop-core-update:UI:ConfirmUpdate' => 'Application Upgrade',
'itop-core-update:UI:UpdateCoreFiles' => 'Application Upgrade',
'itop-core-update:UI:UpdateDone' => 'Application Upgrade',
'iTopUpdate:UI:SelectUpdateFile' => 'Select an upgrade file to upload',
'iTopUpdate:UI:CheckUpdate' => 'Verify upgrade file',
'iTopUpdate:UI:ConfirmInstallFile' => 'You are about to install %1$s',
'iTopUpdate:UI:DoUpdate' => 'Upgrade',
'iTopUpdate:UI:CurrentVersion' => 'Current installed version',
'iTopUpdate:UI:Back' => 'Back',
'iTopUpdate:UI:Cancel' => 'Cancel',
'iTopUpdate:UI:Continue' => 'Continue',
'iTopUpdate:UI:WithDBBackup' => 'Database backup',
'iTopUpdate:UI:WithFilesBackup' => 'Application files backup',
'iTopUpdate:UI:WithoutBackup' => 'No backup is planned',
'iTopUpdate:UI:Backup' => 'Backup generated before update',
'iTopUpdate:UI:DoFilesArchive' => 'Archive application files',
'iTopUpdate:UI:UploadArchive' => 'Select a package to upload',
'iTopUpdate:UI:ServerFile' => 'Path of a package already on the server',
'iTopUpdate:UI:Status' => 'Status',
'iTopUpdate:UI:Action' => 'Update',
'iTopUpdate:UI:History' => 'Versions History',
'iTopUpdate:UI:Progress' => 'Progress of the upgrade',
'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database',
'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space',
'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space',
'iTopUpdate:UI:ItopDiskSpace' => 'iTop disk space',
'iTopUpdate:UI:DBDiskSpace' => 'Database disk space',
'iTopUpdate:UI:FileUploadMaxSize' => 'File upload max size',
'iTopUpdate:UI:PostMaxSize' => 'PHP ini value post_max_size: %1$s',
'iTopUpdate:UI:UploadMaxFileSize' => 'PHP ini value upload_max_filesize: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Loading' => 'Checking filesystem',
'iTopUpdate:UI:CanCoreUpdate:Error' => 'Checking filesystem failed (%1$s)',
'iTopUpdate:UI:CanCoreUpdate:ErrorFileNotExist' => 'Checking filesystem failed (File not exist %1$s)',
'iTopUpdate:UI:CanCoreUpdate:Failed' => 'Checking filesystem failed',
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'Application can be updated',
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start',
'iTopUpdate:UI:SetupMessage:EnterMaintenance' => 'Entering maintenance mode',
'iTopUpdate:UI:SetupMessage:Backup' => 'Database backup',
'iTopUpdate:UI:SetupMessage:FilesArchive' => 'Archive application files',
'iTopUpdate:UI:SetupMessage:CopyFiles' => 'Copy new version files',
'iTopUpdate:UI:SetupMessage:Compile' => 'Upgrade application and database',
'iTopUpdate:UI:SetupMessage:ExitMaintenance' => 'Exiting maintenance mode',
'iTopUpdate:UI:SetupMessage:UpdateDone' => 'Upgrade completed',
// Errors
'iTopUpdate:Error:MissingFunction' => 'Impossible to start upgrade, missing function',
'iTopUpdate:Error:MissingFile' => 'Missing file: %1$s',
'iTopUpdate:Error:CorruptedFile' => 'File %1$s is corrupted',
'iTopUpdate:Error:BadFileFormat' => 'Upgrade file is not a zip file',
'iTopUpdate:Error:BadFileContent' => 'Upgrade file is not an application archive',
'iTopUpdate:Error:BadItopProduct' => 'Upgrade file is not compatible with your application',
'iTopUpdate:Error:Copy' => 'Error, cannot copy \'%1$s\' to \'%2$s\'',
'iTopUpdate:Error:FileNotFound' => 'File not found',
'iTopUpdate:Error:NoFile' => 'No file provided',
'iTopUpdate:Error:InvalidToken' => 'Invalid token',
'iTopUpdate:Error:UpdateFailed' => 'Upgrade failed ',
'iTopUpdate:Error:FileUploadMaxSizeTooSmall' => 'The upload max size seems too small for update. Please change the PHP configuration.',
'iTopUpdate:UI:RestoreArchive' => 'You can restore your application from the archive \'%1$s\'',
'iTopUpdate:UI:RestoreBackup' => 'You can restore the database from \'%1$s\'',
'iTopUpdate:UI:MaintenanceModeActive' => 'The application is currently under maintenance, no user can access the application. You have to run a setup or restore the application archive to return in normal mode.',
'iTopUpdate:UI:UpdateDone' => 'Upgrade successful',
'Menu:iTopUpdate' => 'Application Upgrade',
'Menu:iTopUpdate+' => 'Application Upgrade',
// Missing itop entries
'Class:ModuleInstallation/Attribute:installed' => 'Installed on',
'Class:ModuleInstallation/Attribute:name' => 'Name',
'Class:ModuleInstallation/Attribute:version' => 'Version',
'Class:ModuleInstallation/Attribute:comment' => 'Comment',
));

View File

@@ -0,0 +1,108 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('FR FR', 'French', 'Français', array(
'iTopUpdate:UI:PageTitle' => 'Mise à jour de l\'application',
'itop-core-update:UI:SelectUpdateFile' => 'Mise à jour',
'itop-core-update:UI:ConfirmUpdate' => 'Mise à jour',
'itop-core-update:UI:UpdateCoreFiles' => 'Mise à jour',
'iTopUpdate:UI:SelectUpdateFile' => 'Sélectionner un fichier de mise à jour',
'iTopUpdate:UI:CheckUpdate' => 'Vérification de la mise à jour',
'iTopUpdate:UI:ConfirmInstallFile' => 'La mise à jour %1$s va être installée',
'iTopUpdate:UI:DoUpdate' => 'Mettre à jour',
'iTopUpdate:UI:DoBackup' => 'Faire une sauvegarde des fichiers et de la base',
'iTopUpdate:UI:CurrentVersion' => 'Version installée',
'iTopUpdate:UI:Back' => 'Annuler',
'iTopUpdate:UI:WithBackup' => 'Avec sauvegarde de l\'application, l\'archive sera dans \'%1$s\'',
'iTopUpdate:UI:WithoutBackup' => 'Pas de sauvegarde',
'iTopUpdate:UI:Status' => 'Versions installées',
'iTopUpdate:UI:InstallationCanBeUpdated' => 'L\'application peut être mise à jour',
'iTopUpdate:UI:InstallationCanNotBeUpdated' => 'L\'application ne peut pas être mise à jour',
'iTopUpdate:UI:DiskFreeSpace' => 'Taille disque disponible',
'iTopUpdate:UI:ItopDiskSpace' => 'Taille disque utilisée par l\'application',
'iTopUpdate:UI:DBDiskSpace' => 'Taille disque utilisée par la base de données',
'iTopUpdate:UI:FileUploadMaxSize' => 'Taille maximale de chargement de fichier',
'iTopUpdate:UI:PostMaxSize' => 'Valeur PHP ini post_max_size : %1$s',
'iTopUpdate:UI:UploadMaxFileSize' => 'Valeur PHP ini upload_max_filesize : %1$s',
'iTopUpdate:UI:Cancel' => 'Annuler',
'iTopUpdate:UI:Continue' => 'Continuer',
'iTopUpdate:UI:WithDBBackup' => 'Sauvegarde de la base de données',
'iTopUpdate:UI:WithFilesBackup' => 'Archive des fichiers de l\'application' ,
'iTopUpdate:UI:Backup' => 'Sauvegarde effectuée avant la mise à jour',
'iTopUpdate:UI:DoFilesArchive' => 'Archive les fichiers de l\'application',
'iTopUpdate:UI:Action' => 'Mettre à jour',
'iTopUpdate:UI:History' => 'Historique des versions',
'iTopUpdate:UI:Progress' => 'Progression de la mise à jour',
'iTopUpdate:UI:DoBackup:Label' => 'Sauvegarde de la base de données',
'iTopUpdate:UI:DoBackup:Warning' => 'La sauvegarde n\'est pas conseillée à cause du manque de place disque disponible',
'iTopUpdate:UI:CanCoreUpdate:Loading' => 'Vérification des fichiers',
'iTopUpdate:UI:CanCoreUpdate:Error' => 'Échec de la vérification des fichiers (%1$s)',
'iTopUpdate:UI:CanCoreUpdate:ErrorFileNotExist' => 'Échec de la vérification des fichiers (Fichier manquant %1$s)',
'iTopUpdate:UI:CanCoreUpdate:Failed' => 'Échec de la vérification des fichiers',
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'L\'application peut être mise à jour',
'iTopUpdate:UI:CanCoreUpdate:No' => 'L\'application ne peut pas être mise à jour : %1$s',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Prêt pour l\installation',
'iTopUpdate:UI:SetupMessage:EnterMaintenance' => 'Application en maintenance',
'iTopUpdate:UI:SetupMessage:Backup' => 'Sauvegarde des fichiers de l\'application',
'iTopUpdate:UI:SetupMessage:FilesArchive' => 'Archivage des fichiers de l\'application',
'iTopUpdate:UI:SetupMessage:CopyFiles' => 'Copie des fichiers de la nouvelle version',
'iTopUpdate:UI:SetupMessage:Compile' => 'Mise à jour de l\'application et de la base de données',
'iTopUpdate:UI:SetupMessage:ExitMaintenance' => 'Application en utilisation normale',
'iTopUpdate:UI:SetupMessage:UpdateDone' => 'Installation terminée',
// Errors
'iTopUpdate:Error:MissingFunction' => 'Impossible de mettre à jour',
'iTopUpdate:Error:MissingFile' => 'Ficher manquant : %1$s',
'iTopUpdate:Error:CorruptedFile' => 'Le fichier %1$s est corrompu',
'iTopUpdate:Error:BadFileFormat' => 'Le fichier de mise à jour n\'est pas au format "zip"' ,
'iTopUpdate:Error:Copy' => 'Erreur : impossible de copier le fichier \'%1$s\' dans \'%2$s\'',
'iTopUpdate:Error:FileNotFound' => 'Fichier manquant',
'iTopUpdate:Error:InvalidToken' => 'Information manquante',
'iTopUpdate:Error:NoUpdate' => 'La mise à jour a échoué',
'iTopUpdate:Error:FileUploadMaxSizeTooSmall' => 'La taille maximale de chargement de fichier semble trop petite pour faire la mise à jour. Veuillez changer la configuration de PHP.',
'iTopUpdate:UI:RestoreArchive' => 'Vous pouvez restaurer l\'application depuis \'%1$s\'',
'iTopUpdate:UI:RestoreBackup' => 'Vous pouvez restaurer la base de données depuis \'%1$s\'',
'iTopUpdate:UI:MaintenanceModeActive' => 'L\'application est actuellement en maintenance, aucun utilisateur n\'a accès à l\'application. Vous pouvez lancer un setup ou réinstaller l\'application depuis une archive pour retourner dans un mode normal.',
'iTopUpdate:UI:UpdateDone' => 'Mise à jour effectuée',
'Menu:iTopUpdate' => 'Mise à jour de l\'application',
'Menu:iTopUpdate+' => 'Mise à jour de l\'application',
'iTopUpdate:Error:BadFileContent' => 'Le fichier n\'est pas une archive valide',
'iTopUpdate:Error:BadItopProduct' => 'L\'archive n\'est pas compatible avec votre application',
'iTopUpdate:Error:NoFile' => 'Pas d\'archive',
'iTopUpdate:Error:UpdateFailed' => 'La mise à jour à échoué ',
// Missing itop entries
'Class:ModuleInstallation/Attribute:installed' => 'Installé le',
'Class:ModuleInstallation/Attribute:name' => 'Nom',
'Class:ModuleInstallation/Attribute:version' => 'Version',
'Class:ModuleInstallation/Attribute:comment' => 'Commentaire',
));

View File

@@ -0,0 +1,19 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\CoreUpdate;
use Combodo\iTop\CoreUpdate\Controller\UpdateController;
use ContextTag;
require_once(APPROOT.'application/startup.inc.php');
new ContextTag('Setup');
$oUpdateController = new UpdateController();
$oUpdateController->DisableInDemoMode();
$oUpdateController->AllowOnlyAdmin();
$oUpdateController->SetDefaultOperation('SelectUpdateFile');
$oUpdateController->HandleOperation();

View File

@@ -0,0 +1,52 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-core-update/1.0.0',
array(
// Identification
//
'label' => 'iTop Core Update',
'category' => 'business',
// Setup
//
'dependencies' => array(
'itop-twig-base/1.0.0',
'itop-files-information/1.0.0',
'combodo-db-tools/1.0.8',
),
'mandatory' => false,
'visible' => true,
// Components
//
'datamodel' => array(
'model.itop-core-update.php',
'src/Service/RunTimeEnvironmentCoreUpdater.php',
'src/Service/CoreUpdater.php',
'src/Controller/UpdateController.php',
'src/Controller/AjaxController.php',
),
'webservice' => array(),
'data.struct' => array(),
'data.sample' => array(),
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => array(),
)
);

View File

@@ -0,0 +1,184 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\CoreUpdate\Controller;
use Combodo\iTop\CoreUpdate\Service\CoreUpdater;
use Combodo\iTop\DBTools\Service\DBToolsUtils;
use Combodo\iTop\FilesInformation\Service\FileNotExistException;
use Combodo\iTop\FilesInformation\Service\FilesInformation;
use Combodo\iTop\TwigBase\Controller\Controller;
use Dict;
use Exception;
use IssueLog;
use MetaModel;
use SetupUtils;
use utils;
class AjaxController extends Controller
{
const LOCAL_DIR = __DIR__;
public function OperationCanUpdateCore()
{
$aParams = array();
try
{
$bCanUpdateCore = FilesInformation::CanUpdateCore($sMessage);
$aParams['bStatus'] = $bCanUpdateCore;
if ($bCanUpdateCore)
{
$aParams['sMessage'] = Dict::S('iTopUpdate:UI:CanCoreUpdate:Yes');
}
else
{
$aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CanCoreUpdate:No', $sMessage);
}
}
catch (FileNotExistException $e)
{
$aParams['bStatus'] = false;
$aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CanCoreUpdate:ErrorFileNotExist', $e->getMessage());
}
catch(Exception $e)
{
$aParams['bStatus'] = false;
$aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CanCoreUpdate:Error', $e->getMessage());
}
$this->DisplayJSONPage($aParams);
}
public function OperationGetItopDiskSpace()
{
$aParams = array();
$aParams['iItopDiskSpace'] = FilesInformation::GetItopDiskSpace();
$aParams['sItopDiskSpace'] = utils::BytesToFriendlyFormat($aParams['iItopDiskSpace']);
$this->DisplayJSONPage($aParams);
}
public function OperationGetDBDiskSpace()
{
$aParams = array();
$aParams['iDBDiskSpace'] = DBToolsUtils::GetDatabaseSize();
$aParams['sDBDiskSpace'] = utils::BytesToFriendlyFormat($aParams['iDBDiskSpace']);
$this->DisplayJSONPage($aParams);
}
public function OperationGetCurrentVersion()
{
$aParams = array();
$aParams['sVersion'] = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
$this->DisplayJSONPage($aParams);
}
public function OperationEnterMaintenance()
{
$aParams = array();
try
{
SetupUtils::EnterReadOnlyMode(MetaModel::GetConfig());
$iResponseCode = 200;
}
catch (Exception $e)
{
IssueLog::Error("EnterMaintenance: ".$e->getMessage());
$aParams['sError'] = $e->getMessage();
$iResponseCode = 500;
}
$this->DisplayJSONPage($aParams, $iResponseCode);
}
public function OperationExitMaintenance()
{
$aParams = array();
try
{
SetupUtils::ExitReadOnlyMode();
$iResponseCode = 200;
}
catch (Exception $e)
{
IssueLog::Error("ExitMaintenance: ".$e->getMessage());
$aParams['sError'] = $e->getMessage();
$iResponseCode = 500;
}
$this->DisplayJSONPage($aParams, $iResponseCode);
}
public function OperationBackup()
{
$aParams = array();
try
{
CoreUpdater::Backup();
$iResponseCode = 200;
}
catch (Exception $e)
{
IssueLog::Error("Backup: ".$e->getMessage());
$aParams['sError'] = $e->getMessage();
$iResponseCode = 500;
}
$this->DisplayJSONPage($aParams, $iResponseCode);
}
public function OperationFilesArchive()
{
$aParams = array();
try
{
CoreUpdater::CreateItopArchive();
$iResponseCode = 200;
}
catch (Exception $e)
{
IssueLog::Error("FilesArchive: ".$e->getMessage());
$aParams['sError'] = $e->getMessage();
$iResponseCode = 500;
}
$this->DisplayJSONPage($aParams, $iResponseCode);
}
public function OperationCopyFiles()
{
$aParams = array();
try
{
CoreUpdater::CopyCoreFiles();
$iResponseCode = 200;
}
catch (Exception $e)
{
IssueLog::Error("CopyFiles: ".$e->getMessage());
$aParams['sError'] = $e->getMessage();
$iResponseCode = 500;
}
$this->DisplayJSONPage($aParams, $iResponseCode);
}
public function OperationCompile()
{
$aParams = array();
try
{
CoreUpdater::Compile();
$iResponseCode = 200;
}
catch (Exception $e)
{
IssueLog::Error("Compile: ".$e->getMessage());
$aParams['sError'] = $e->getMessage();
$iResponseCode = 500;
}
$this->DisplayJSONPage($aParams, $iResponseCode);
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\CoreUpdate\Controller;
use Combodo\iTop\CoreUpdate\Service\CoreUpdater;
use Combodo\iTop\DBTools\Service\DBToolsUtils;
use Combodo\iTop\TwigBase\Controller\Controller;
use Dict;
use Exception;
use SetupUtils;
use utils;
class UpdateController extends Controller
{
const LOCAL_DIR = __DIR__;
public function OperationSelectUpdateFile()
{
$sTransactionId = utils::GetNewTransactionId();
$aParams = array();
$aParams['sTransactionId'] = $sTransactionId;
$aParams['aPreviousInstall'] = $this->GetPreviousInstallations();
$aParams['sAjaxURL'] = utils::GetAbsoluteUrlModulePage('itop-core-update', 'ajax.php', array('maintenance' => 'true'));
$aParams['iDiskFreeSpace'] = disk_free_space(APPROOT);
$aParams['sDiskFreeSpace'] = utils::BytesToFriendlyFormat($aParams['iDiskFreeSpace']);
$aParams['iFileUploadMaxSize'] = $this->GetFileUploadMaxSize();
$aParams['sFileUploadMaxSize'] = utils::BytesToFriendlyFormat($aParams['iFileUploadMaxSize']);
$aParams['sPostMaxSize'] = ini_get('post_max_size');
$aParams['sUploadMaxSize'] = ini_get('upload_max_filesize');
$this->DisplayPage($aParams);
}
/**
* @throws \Exception
*/
public function OperationConfirmUpdate()
{
$sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id');
if (!utils::IsTransactionValid($sTransactionId))
{
throw new Exception(Dict::S('iTopUpdate:Error:InvalidToken'));
}
$bDoBackup = utils::ReadPostedParam('doBackup', 0, 'integer') == 1;
$bDoFilesArchive = utils::ReadPostedParam('doFilesArchive', 0, 'integer') == 1;
$sNewVersion = null;
$sName = '';
$sVersionToInstall = '';
$sError = '';
try
{
if (isset($_FILES['file']))
{
$aFileInfo = $_FILES['file'];
$iError = $aFileInfo['error'];
if ($iError === UPLOAD_ERR_OK)
{
$sDownloadDir = CoreUpdater::DOWNLOAD_DIR;
if (is_dir($sDownloadDir))
{
SetupUtils::rrmdir($sDownloadDir);
}
SetupUtils::builddir($sDownloadDir);
$sTmpName = $aFileInfo['tmp_name'];
$sName = $aFileInfo['name'];
$sNewVersion = $sDownloadDir.$sName;
if (@move_uploaded_file($sTmpName, $sNewVersion) === false)
{
throw new Exception(Dict::S('iTopUpdate:Error:FileNotFound'));
}
CoreUpdater::ExtractDownloadedFile($sNewVersion);
$sVersionToInstall = CoreUpdater::GetVersionToInstall();
}
else
{
throw new Exception(Dict::S('iTopUpdate:Error:NoFile'));
}
}
else
{
throw new Exception(Dict::S('iTopUpdate:Error:NoFile'));
}
}
catch (Exception $e)
{
$iError = UPLOAD_ERR_NO_FILE;
$sError = $e->getMessage();
}
$aParams = array();
$aParams['sName'] = $sName;
$aParams['bSuccess'] = ($iError == 0);
$aParams['sError'] = $sError;
$aParams['bDoBackup'] = $bDoBackup;
$aParams['bDoFilesArchive'] = $bDoFilesArchive;
$aParams['sItopArchive'] = CoreUpdater::GetItopArchiveFile();
$aParams['sBackupFile'] = CoreUpdater::GetBackupFile();
$sQuestion = Dict::Format('iTopUpdate:UI:ConfirmInstallFile', $sVersionToInstall);
$aParams['sQuestion'] = $sQuestion;
$sTransactionId = utils::GetNewTransactionId();
$aParams['sTransactionId'] = $sTransactionId;
$this->DisplayPage($aParams);
}
public function OperationUpdateCoreFiles()
{
$sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id');
if (!utils::IsTransactionValid($sTransactionId))
{
throw new Exception(Dict::S('iTopUpdate:Error:InvalidToken'));
}
$sNewVersion = utils::ReadPostedParam('filename', null, 'filename');
$bDoBackup = utils::ReadPostedParam('doBackup', 0, 'integer') == 1;
$bDoFilesArchive = utils::ReadPostedParam('doFilesArchive', 0, 'integer') == 1;
$sCurrentVersion = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
$aParams = array(
'sCurrentVersion' => $sCurrentVersion,
'bDoBackup' => $bDoBackup,
'sBackupFile' => CoreUpdater::GetBackupFile(),
'bDoFilesArchive' => $bDoFilesArchive,
'sItopArchive' => CoreUpdater::GetItopArchiveFile(),
'sNewVersion' => $sNewVersion,
'sProgressImage' => utils::GetAbsoluteUrlAppRoot().'setup/orange-progress.gif',
'sSetupToken' => SetupUtils::CreateSetupToken(),
'sAjaxURL' => utils::GetAbsoluteUrlModulePage('itop-core-update', 'ajax.php', array('maintenance' => 'true')),
);
$this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().'setup/jquery.progression.js');
$this->DisplayPage($aParams);
}
private function GetPreviousInstallations()
{
return DBToolsUtils::GetPreviousInstallations();
}
// Returns a file size limit in bytes based on the PHP upload_max_filesize
// and post_max_size
private function GetFileUploadMaxSize()
{
static $iMaxSize = -1;
if ($iMaxSize < 0)
{
// Start with post_max_size.
$iPostMaxSize = $this->ParseSize(ini_get('post_max_size'));
if ($iPostMaxSize > 0)
{
$iMaxSize = $iPostMaxSize;
}
// If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit.
$iUploadMax = $this->ParseSize(ini_get('upload_max_filesize'));
if ($iUploadMax > 0 && $iUploadMax < $iMaxSize)
{
$iMaxSize = $iUploadMax;
}
}
return $iMaxSize;
}
private function ParseSize($iSize)
{
$sUnit = preg_replace('/[^bkmgtpezy]/i', '', $iSize); // Remove the non-unit characters from the size.
$iSize = preg_replace('/[^0-9.]/', '', $iSize); // Remove the non-numeric characters from the size.
if ($sUnit)
{
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
return round($iSize * pow(1024, stripos('bkmgtpezy', $sUnit[0])));
}
else
{
return round($iSize);
}
}
}

View File

@@ -0,0 +1,522 @@
<?php
/**
* iTop
* @copyright Copyright (C) 2010,2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*
*/
namespace Combodo\iTop\CoreUpdate\Service;
use Combodo\iTop\FilesInformation\Service\FilesIntegrity;
use DBBackup;
use Dict;
use Exception;
use IssueLog;
use iTopExtension;
use iTopExtensionsMap;
use iTopMutex;
use MetaModel;
use SetupUtils;
use utils;
use ZipArchive;
require_once APPROOT.'setup/applicationinstaller.class.inc.php';
require_once APPROOT.'setup/runtimeenv.class.inc.php';
final class CoreUpdater
{
const DOWNLOAD_DIR = APPROOT.'data/downloaded-core/';
const UPDATE_DIR = APPROOT.'data/core-update/';
/**
* @param bool $bDoBackup
*
* @throws \Exception
*/
public static function CopyCoreFiles()
{
set_time_limit(600);
// Extract updater file from the new version if available
if (is_file(APPROOT.'setup/appupgradecopy.php'))
{
// Remove previous specific updater
@unlink(APPROOT.'setup/appupgradecopy.php');
}
if (is_file(self::UPDATE_DIR.'web/setup/appupgradecopy.php'))
{
IssueLog::Info('itop-core-update: Use updater provided in the archive');
self::CopyFile(self::UPDATE_DIR.'web/setup/appupgradecopy.php', APPROOT.'setup/appupgradecopy.php');
@include_once(APPROOT.'setup/appupgradecopy.php');
}
try
{
if (function_exists('AppUpgradeCopyFiles'))
{
// start the update
set_time_limit(600);
AppUpgradeCopyFiles(self::UPDATE_DIR.'web/');
}
else
{
// Local function for older iTop versions
IssueLog::Info('itop-core-update: Use default updater');
self::LocalUpdateCoreFiles(self::UPDATE_DIR.'web/');
}
IssueLog::Info('itop-core-update: Update done, check files integrity');
FilesIntegrity::CheckInstallationIntegrity(APPROOT);
IssueLog::Info('itop-core-update: Files integrity OK');
}
catch (Exception $e)
{
IssueLog::error($e->getMessage());
IssueLog::Info('itop-core-update: ended');
throw $e;
}
finally
{
self::RRmdir(self::UPDATE_DIR);
}
}
/**
* @throws \Exception
*/
public static function Compile()
{
try
{
// Compile code
IssueLog::Info('itop-core-update: Start compilation');
IssueLog::Info('itop-core-update: Version Dev');
$sTargetEnv = 'production';
$oRuntimeEnv = new RunTimeEnvironmentCoreUpdater($sTargetEnv);
$oRuntimeEnv->CheckDirectories($sTargetEnv);
$oRuntimeEnv->CompileFrom('production');
$oConfig = $oRuntimeEnv->MakeConfigFile($sTargetEnv.' (built on '.date('Y-m-d').')');
$oConfig->Set('access_mode', ACCESS_FULL);
$oRuntimeEnv->WriteConfigFileSafe($oConfig);
$oRuntimeEnv->InitDataModel($oConfig, true);
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir());
$aSelectedModules = array();
foreach ($aAvailableModules as $sModuleId => $aModule)
{
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE))
{
continue;
}
else
{
$aSelectedModules[] = $sModuleId;
}
}
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
$oRuntimeEnv->UpdatePredefinedObjects();
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
$oRuntimeEnv->LoadData($aAvailableModules, $aSelectedModules, false /* no sample data*/);
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
$oExtensionsMap = new iTopExtensionsMap();
// Default choices = as before
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension)
{
// Plus all "remote" extensions
if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE)
{
$oExtensionsMap->MarkAsChosen($oExtension->sCode);
}
}
$aSelectedExtensionCodes = array();
foreach ($oExtensionsMap->GetChoices() as $oExtension)
{
$aSelectedExtensionCodes[] = $oExtension->sCode;
}
$oRuntimeEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModules,
$aSelectedExtensionCodes, 'Done by the iTop Core Updater');
IssueLog::Info('itop-core-update: Compilation done');
}
catch (Exception $e)
{
IssueLog::error($e->getMessage());
throw $e;
}
}
/**
* @param $sFromDir
*
* @throws \Exception
*/
private static function LocalUpdateCoreFiles($sFromDir)
{
self::CopyDir($sFromDir, APPROOT);
}
/**
* @param $sArchiveFile
*
* @throws \Exception
*/
private static function ExtractUpdateFile($sArchiveFile)
{
if (!utils::EndsWith($sArchiveFile, '.zip'))
{
throw new Exception(Dict::S('iTopUpdate:Error:BadFileFormat'));
}
$oArchive = new ZipArchive();
$oArchive->open($sArchiveFile);
self::RRmdir(self::UPDATE_DIR);
SetupUtils::builddir(self::UPDATE_DIR);
$oArchive->extractTo(self::UPDATE_DIR);
}
/**
* @throws \Exception
*/
public static function Backup()
{
$sBackupName = self::GetBackupName();
$sBackupFile = self::GetBackupFile();
if (file_exists($sBackupFile))
{
@unlink($sBackupFile);
}
self::DoBackup($sBackupName);
}
/**
* @throws \Exception
*/
public static function CreateItopArchive()
{
set_time_limit(0);
$sItopArchiveFile = self::GetItopArchiveFile();
if (file_exists($sItopArchiveFile))
{
@unlink($sItopArchiveFile);
}
$sTempFile = sys_get_temp_dir().'/'.basename($sItopArchiveFile);
if (file_exists($sTempFile))
{
@unlink($sTempFile);
}
$aPathInfo = pathInfo(realpath(APPROOT));
$sParentPath = $aPathInfo['dirname'];
$sDirName = $aPathInfo['basename'];
$oZipArchive = new ZipArchive();
$oZipArchive->open($sTempFile, ZIPARCHIVE::CREATE);
$oZipArchive->addEmptyDir($sDirName);
self::ZipFolder(realpath(APPROOT), $oZipArchive, strlen("$sParentPath/"));
$oZipArchive->close();
if (!file_exists($sTempFile))
{
IssueLog::Error("Failed to create itop archive $sTempFile");
}
if (@rename($sTempFile, $sItopArchiveFile))
{
IssueLog::Info("Archive $sItopArchiveFile Created");
}
else
{
IssueLog::Error("Failed to create archive $sItopArchiveFile");
}
}
/**
*
* @param string $sTargetFile
* @throws Exception
*/
private static function DoBackup($sTargetFile)
{
// Make sure the target directory exists
$sBackupDir = dirname($sTargetFile);
SetupUtils::builddir($sBackupDir);
$oBackup = new DBBackup();
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
$oMutex->Lock();
try
{
$oBackup->CreateCompressedBackup($sTargetFile);
IssueLog::Info('itop-core-update: Backup done: '.$sTargetFile);
}
catch (Exception $e)
{
$oMutex->Unlock();
throw $e;
}
$oMutex->Unlock();
}
/**
* @param $sSource
* @param $sDest
*
* @throws \Exception
*/
public static function CopyDir($sSource, $sDest)
{
if (is_dir($sSource))
{
if (!is_dir($sDest))
{
@mkdir($sDest, 0755);
}
$aFiles = scandir($sSource);
if(sizeof($aFiles) > 0 )
{
foreach($aFiles as $sFile)
{
if ($sFile == '.' || $sFile == '..' || $sFile == '.svn' || $sFile == '.git')
{
// Skip
continue;
}
if (is_dir($sSource.'/'.$sFile))
{
// Recurse
self::CopyDir($sSource.'/'.$sFile, $sDest.'/'.$sFile);
}
else
{
if (is_link($sDest.'/'.$sFile))
{
unlink($sDest.'/'.$sFile);
}
self::CopyFile($sSource.'/'.$sFile, $sDest.'/'.$sFile);
}
}
}
}
elseif (is_file($sSource))
{
self::CopyFile($sSource, $sDest);
}
}
public static function RRmdir($sDir) {
if (is_dir($sDir))
{
$oDir = @opendir($sDir);
while (false !== ($sFile = @readdir($oDir)))
{
if (($sFile != '.') && ($sFile != '..'))
{
$sFull = $sDir.'/'.$sFile;
if (is_dir($sFull))
{
self::RRmdir($sFull);
}
else
{
@unlink($sFull);
}
}
}
@closedir($oDir);
@rmdir($sDir);
}
}
/**
* @param $sSource
* @param $sDest
*
* @throws \Exception
*/
public static function CopyFile($sSource, $sDest)
{
if (is_file($sSource))
{
if (!@copy($sSource, $sDest))
{
// Try changing the mode of the file
@chmod($sDest, 0644);
if (!@copy($sSource, $sDest))
{
throw new Exception(Dict::Format('iTopUpdate:Error:Copy', $sSource, $sDest));
}
}
}
}
/**
* Add files and sub-directories in a folder to zip file.
*
* @param string $sFolder
* @param ZipArchive $oZipArchive
* @param int $iStrippedLength Number of text to be removed from the file path.
*/
private static function ZipFolder($sFolder, &$oZipArchive, $iStrippedLength) {
$oFolder = opendir($sFolder);
while (false !== ($sFile = readdir($oFolder))) {
if (($sFile == '.') || ($sFile == '..'))
{
continue;
}
$sFilePath = "$sFolder/$sFile";
$sLocalItopPath = utils::LocalPath($sFilePath);
if ($sLocalItopPath == 'data/backups' || $sLocalItopPath == 'log')
{
continue;
}
// Remove prefix from file path before add to zip.
$sLocalPath = substr($sFilePath, $iStrippedLength);
if (is_file($sFilePath)) {
$oZipArchive->addFile($sFilePath, $sLocalPath);
} elseif (is_dir($sFilePath)) {
// Add sub-directory.
$oZipArchive->addEmptyDir($sLocalPath);
self::ZipFolder($sFilePath, $oZipArchive, $iStrippedLength);
}
}
closedir($oFolder);
}
/**
* @return string
*/
private static function GetItopArchiveName()
{
$sItopArchiveName = APPROOT.'data/backups/itop';
return $sItopArchiveName;
}
/**
* @return string
*/
public static function GetItopArchiveFile()
{
$sItopArchiveFile = self::GetItopArchiveName().'.zip';
return $sItopArchiveFile;
}
/**
* @return string
*/
private static function GetBackupName()
{
$sBackupName = APPROOT.'data/backups/manual/backup-core-update';
return $sBackupName;
}
/**
* @return string
*/
public static function GetBackupFile()
{
$sBackupFile = self::GetBackupName().'.tar.gz';
return $sBackupFile;
}
/**
* @param $sArchiveFile
*
* @throws \Exception
*/
public static function ExtractDownloadedFile($sArchiveFile)
{
try
{
// Extract archive file
self::ExtractUpdateFile($sArchiveFile);
IssueLog::Info('itop-core-update: Archive extracted, check files integrity');
// Check files integrity
FilesIntegrity::CheckInstallationIntegrity(self::UPDATE_DIR.'web/');
IssueLog::Info('itop-core-update: Files integrity OK');
}
catch (Exception $e)
{
self::RRmdir(self::UPDATE_DIR);
throw $e;
}
finally
{
self::RRmdir(self::DOWNLOAD_DIR);
}
}
/**
* @return string
* @throws \Exception
*/
public static function GetVersionToInstall()
{
try
{
$sConfigFile = self::UPDATE_DIR.'web/core/config.class.inc.php';
if (!is_file($sConfigFile))
{
throw new Exception(Dict::S(Dict::S('iTopUpdate:Error:BadFileContent')));
}
$sContents = file_get_contents($sConfigFile);
preg_match_all("@define\('(?<name>ITOP_[^']*)', '(?<value>[^']*)'\);@", $sContents, $aMatches);
if (empty($aMatches))
{
throw new Exception(Dict::S(Dict::S('iTopUpdate:Error:BadFileContent')));
}
$aValues = array();
foreach ($aMatches['name'] as $index => $sName)
{
$aValues[$sName] = $aMatches['value'][$index];
}
if ($aValues['ITOP_APPLICATION'] != ITOP_APPLICATION)
{
throw new Exception(Dict::S('iTopUpdate:Error:BadItopProduct'));
}
// Extract updater file from the new version if available
if (is_file(APPROOT.'setup/appupgradecheck.php'))
{
// Remove previous specific updater
@unlink(APPROOT.'setup/appupgradecheck.php');
}
if (is_file(self::UPDATE_DIR.'web/setup/appupgradecheck.php'))
{
IssueLog::Info('itop-core-update: Use updater provided in the archive');
self::CopyFile(self::UPDATE_DIR.'web/setup/appupgradecheck.php', APPROOT.'setup/appupgradecheck.php');
@include_once(APPROOT.'setup/appupgradecheck.php');
}
if (function_exists('AppUpgradeCheckInstall'))
{
AppUpgradeCheckInstall();
}
return Dict::Format('UI:iTopVersion:Long', $aValues['ITOP_APPLICATION'], $aValues['ITOP_VERSION'], $aValues['ITOP_REVISION'], $aValues['ITOP_BUILD_DATE']);
}
catch (Exception $e)
{
self::RRmdir(self::UPDATE_DIR);
self::RRmdir(self::DOWNLOAD_DIR);
throw $e;
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\CoreUpdate\Service;
require_once(APPROOT."setup/runtimeenv.class.inc.php");
use Config;
use Exception;
use RunTimeEnvironment;
class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment
{
public function CheckDirectories($sTargetEnv)
{
$sTargetDir = APPROOT.'env-'.$sTargetEnv;
$sBuildDir = $sTargetDir.'-build';
self::CheckDirectory($sTargetDir);
self::CheckDirectory($sBuildDir);
}
/**
* @param $sDir
* @throws Exception
*/
public static function CheckDirectory($sDir)
{
if (!is_dir($sDir))
{
if (!@mkdir($sDir,0770))
{
throw new Exception('Creating directory '.$sDir.' is denied (Check access rights)');
}
}
// Try create a file
$sTempFile = $sDir.'/__itop_temp_file__';
if (!@touch($sTempFile))
{
throw new Exception('Write access to '.$sDir.' is denied (Check access rights)');
}
@unlink($sTempFile);
}
public function MakeConfigFile($sEnvironmentLabel = null)
{
$oConfig = $this->GetConfig();
if (!is_null($oConfig))
{
// Return the existing one
$oConfig->UpdateIncludes('env-'.$this->sTargetEnv);
}
else
{
// Clone the default 'production' config file
//
$oConfig = clone($this->GetConfig('production'));
$oConfig->UpdateIncludes('env-'.$this->sTargetEnv);
if (is_null($sEnvironmentLabel))
{
$sEnvironmentLabel = $this->sTargetEnv;
}
$oConfig->Set('app_env_label', $sEnvironmentLabel);
if ($this->sFinalEnv !== 'production')
{
$oConfig->Set('db_name', $oConfig->Get('db_name').'_'.$this->sFinalEnv);
}
}
return $oConfig;
}
protected function GetConfig($sEnvironment = null)
{
if (is_null($sEnvironment))
{
$sEnvironment = $this->sTargetEnv;
}
$sFile = APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
if (file_exists($sFile))
{
$oConfig = new Config($sFile);
return $oConfig;
}
else
{
return null;
}
}
}

View File

@@ -0,0 +1,110 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<div class="page_header"><h1>{{ 'iTopUpdate:UI:PageTitle'|dict_s }}</h1></div>
<div class="display_block display-files">
{% if bSuccess %}
<fieldset>
<legend>{{ 'iTopUpdate:UI:Status'|dict_s }}</legend>
<div class="header_message message_info">
<div>
<span>{{ sQuestion }}</span>
</div>
</div>
<table style="vertical-align:top" class="one-col-details" data-mode="view">
<tbody>
<tr>
<td style="vertical-align:top" class="">
{{ include('DisplayCurrentVersion.html.twig') }}
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset>
<legend>{{ 'iTopUpdate:UI:Backup'|dict_s }}</legend>
{% if bDoBackup or bDoFilesArchive %}
<table style="vertical-align:top" class="one-col-details" data-mode="view">
<tbody>
<tr>
<td style="vertical-align:top" class="">
{% if bDoBackup %}
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:WithDBBackup'|dict_s }}</span></div>
<div class="field_data">
<div class="field_value">{{ sBackupFile }}</div>
</div>
</div>
</div>
{% endif %}
{% if bDoFilesArchive %}
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:WithFilesBackup'|dict_s }}</span></div>
<div class="field_data">
<div class="field_value">{{ sItopArchive }}</div>
</div>
</div>
</div>
{% endif %}
</td>
</tr>
</tbody>
</table>
{% else %}
<div class="header_message message_info">
<div>
<span>{{ 'iTopUpdate:UI:WithoutBackup'|dict_s }}</span>
</div>
</div>
{% endif %}
</fieldset>
<fieldset>
<legend>{{ 'iTopUpdate:UI:Action'|dict_s }}</legend>
<div>
<table>
<tr>
<td>
<form enctype="multipart/form-data" method="post">
<p><input id="cancel-update" type="submit" value="{{ 'iTopUpdate:UI:Cancel'|dict_s }}"/></p>
</form>
</td>
<td>
<form enctype="multipart/form-data" method="post">
<p><input id="do-update" type="submit" value="{{ 'iTopUpdate:UI:DoUpdate'|dict_s }}"/> <i id="submit-wait" style="display: none" class="fas fa-spinner fa-spin" aria-hidden="true"></i></p>
<input type="hidden" name="operation" value="UpdateCoreFiles"/>
<input type="hidden" name="transaction_id" value="{{ sTransactionId }}">
<input type="hidden" name="filename" value="{{ sName }}">
<input type="hidden" name="doBackup" value="{{ bDoBackup }}">
<input type="hidden" name="doFilesArchive" value="{{ bDoFilesArchive }}">
</form>
</td>
</tr>
</table>
</div>
</fieldset>
{% else %}
<fieldset>
<div class="header_message message_error">
<div>
<span>{{ sError }}</span>
</div>
</div>
<form enctype="multipart/form-data" method="post">
<p><input type="submit" value="{{ 'iTopUpdate:UI:Back'|dict_s }}"/></p>
</form>
</fieldset>
{% endif %}
</div>

View File

@@ -0,0 +1,12 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
$("#do-update").on("click", function() {
$("#submit-wait").show();
$(this).prop("disabled", true);
$("#cancel-update").prop("disabled", true);
$(this).parents('form').submit();
e.preventDefault();
e.stopPropagation();
return false;
});

View File

@@ -0,0 +1,10 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% set sVersion = 'UI:iTopVersion:Long'|dict_format(constant('ITOP_APPLICATION'), constant('ITOP_VERSION'), constant('ITOP_REVISION'), constant('ITOP_BUILD_DATE')) %}
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:CurrentVersion'|dict_s }}</span></div>
<div class="field_data"><div class="field_value">{{ sVersion }}</div></div>
</div>
</div>

View File

@@ -0,0 +1,134 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<div class="page_header"><h1>{{ 'iTopUpdate:UI:PageTitle'|dict_s }}</h1></div>
<div class="display_block display-files">
<fieldset>
<legend>{{ 'iTopUpdate:UI:Status'|dict_s }}</legend>
<div id="header-requirements" class="header_message message_info">
<div>
<span id="can-core-update">{{ 'iTopUpdate:UI:CanCoreUpdate:Loading'|dict_s }} <i class="ajax-spin fas fa-spinner fa-spin" aria-hidden="true"></i></span>
</div>
</div>
<table style="vertical-align:top" class="one-col-details" data-mode="view">
<tbody>
<tr>
<td style="vertical-align:top" class="">
{{ include('DisplayCurrentVersion.html.twig') }}
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:DiskFreeSpace'|dict_s }}</span></div>
<div class="field_data"><div class="field_value">{{ sDiskFreeSpace }}</div></div>
</div>
</div>
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:ItopDiskSpace'|dict_s }}</span></div>
<div class="field_data"><div id="itop-disk-space" class="field_value"><i class="ajax-spin fas fa-spinner fa-spin" aria-hidden="true"></i></div></div>
</div>
</div>
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:DBDiskSpace'|dict_s }}</span></div>
<div class="field_data"><div id="db-disk-space" class="field_value"><i class="ajax-spin fas fa-spinner fa-spin" aria-hidden="true"></i></div></div>
</div>
</div>
<div class="details">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:FileUploadMaxSize'|dict_s }}</span></div>
<div class="field_data"><div class="field_value">{{ sFileUploadMaxSize }}</div></div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset>
<legend>{{ 'iTopUpdate:UI:SelectUpdateFile'|dict_s }}</legend>
<div class="details">
<form enctype="multipart/form-data" method="post">
<input type="hidden" name="operation" value="ConfirmUpdate"/>
<input type="hidden" name="transaction_id" value="{{ sTransactionId }}">
<div id="header-file-size" class="header_message message_error" style="display: none">
<div>
<span>{{ 'iTopUpdate:Error:FileUploadMaxSizeTooSmall'|dict_s }}</span>
</div>
<div>
<span>{{ 'iTopUpdate:UI:PostMaxSize'|dict_format(sPostMaxSize) }}</span>
</div>
<div>
<span>{{ 'iTopUpdate:UI:UploadMaxFileSize'|dict_format(sUploadMaxSize) }}</span>
</div>
</div>
<div class="field_container field_large">
<div class="field_data">
<div class="upload_container">
<input type="file" id="file" name="file"/>
</div>
</div>
<div id="dobackup-warning" class="header_message message_info" style="display: none">{{ 'iTopUpdate:UI:DoBackup:Warning'|dict_s }}</div>
<div class="field_data">
<label id="dobackup-label" for="doBackup">{{ 'iTopUpdate:UI:DoBackup:Label'|dict_s }}</label>
<input type="checkbox" id="doBackup" name="doBackup" checked="checked" value="1"/>
</div>
<div class="field_data">
<label id="dofilesarchive-label" for="doFilesArchive">{{ 'iTopUpdate:UI:DoFilesArchive'|dict_s }}</label>
<input type="checkbox" id="doFilesArchive" name="doFilesArchive" checked="checked" value="1"/>
</div>
</div>
<p><input id="check-update" type="submit" value="{{ 'iTopUpdate:UI:CheckUpdate'|dict_s }}"/> <i id="submit-wait" style="display: none" class="fas fa-spinner fa-spin" aria-hidden="true"></i></p>
</form>
</div>
</fieldset>
<fieldset>
<legend>{{ 'iTopUpdate:UI:History'|dict_s }}</legend>
<div class="details">
{% for aInstall in aPreviousInstall %}
{% if loop.first %}
<table class="listResults">
<tr>
<th>{{ 'Class:ModuleInstallation/Attribute:name'|dict_s }}</th>
<th>{{ 'Class:ModuleInstallation/Attribute:version'|dict_s }}</th>
<th>{{ 'Class:ModuleInstallation/Attribute:installed'|dict_s }}</th>
<th>{{ 'Class:ModuleInstallation/Attribute:comment'|dict_s }}</th>
</tr>
{% endif %}
{% if (loop.index0 % 2) == 0 %}
{% set sRowClass = "odd" %}
{% else %}
{% set sRowClass = "even" %}
{% endif %}
<tr class="{{ sRowClass }}">
<td>{{ aInstall.name }}</td>
<td>{{ aInstall.version }}</td>
<td>{{ aInstall.installed }}</td>
<td>{{ aInstall.comment }}</td>
</tr>
{% if loop.last %}
</table>
{% endif %}
{% endfor %}
</div>
</fieldset>
</div>

View File

@@ -0,0 +1,95 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
var iDiskFreeSpace = {{ iDiskFreeSpace }};
$.ajax({
method: "POST",
url: "{{ sAjaxURL|raw }}",
data: {
"operation": "CanUpdateCore"
},
dataType: "json",
success: function(data)
{
var oRequirements = $("#header-requirements");
var oCanCoreUpdate = $("#can-core-update");
oCanCoreUpdate.html(data.sMessage);
oRequirements.removeClass("message_info");
if (data.bStatus)
{
oRequirements.addClass("message_ok");
}
else
{
oRequirements.addClass("message_error");
}
}
});
var oGetItopDiskSpace = $.ajax({
method: "POST",
url: "{{ sAjaxURL|raw }}",
data: {
"operation": "GetItopDiskSpace"
},
dataType: "json",
success: function(data)
{
var oRequirement = $("#itop-disk-space");
oRequirement.html(data.sItopDiskSpace);
}
});
var oGetDBDiskSpace = $.ajax({
method: "POST",
url: "{{ sAjaxURL|raw }}",
data: {
"operation": "GetDBDiskSpace"
},
dataType: "json",
success: function(data)
{
var oRequirement = $("#db-disk-space");
oRequirement.html(data.sDBDiskSpace);
}
});
$.when(oGetItopDiskSpace, oGetDBDiskSpace).then(
function(data1, data2)
{
var iItopDiskSpace = data1[0].iItopDiskSpace;
var iDBDiskSpace = data2[0].iDBDiskSpace;
if ((2 * (iItopDiskSpace + iDBDiskSpace)) > iDiskFreeSpace)
{
$("#dobackup-warning").show();
}
}
);
$("#file").on("change", function(e) {
var selectedFile = $('#file').get(0).files[0];
var errorMsg = $("#header-file-size");
var submitButton = $("#check-update");
if (selectedFile)
{
if (selectedFile.size > {{ iFileUploadMaxSize }})
{
errorMsg.show();
submitButton.prop("disabled", true);
return;
}
}
errorMsg.hide();
submitButton.prop("disabled", false);
});
$("#check-update").on("click", function(e) {
$("#submit-wait").show();
$(this).prop("disabled", true);
$(".ajax-spin").removeClass("fa-spinner").removeClass("fa-spin").addClass("fa-times");
$(this).parents('form').submit();
e.preventDefault();
e.stopPropagation();
return false;
});

View File

@@ -0,0 +1,90 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<div class="page_header"><h1>{{ 'iTopUpdate:UI:PageTitle'|dict_s }}</h1></div>
<div class="display_block display-files">
<fieldset id="installation_progress">
<legend>{{ 'iTopUpdate:UI:Status'|dict_s }}</legend>
<div id="current_version" class="header_message message_info">
<div>
<span id="current-version">{{ sCurrentVersion }}</span>
</div>
</div>
<table style="vertical-align:top" class="one-col-details" data-mode="view">
<tbody>
<tr>
<td style="vertical-align:top" class="">
{% if bDoBackup %}
<div id="do_backup_done" class="details" style="display: none;">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:WithDBBackup'|dict_s }}</span></div>
<div class="field_data">
<div class="field_value">{{ sBackupFile }}</div>
</div>
</div>
</div>
{% endif %}
{% if bDoFilesArchive %}
<div id="do_files_archive_done" class="details" style="display: none;">
<div class="field_container field_small">
<div class="field_label label"><span title="">{{ 'iTopUpdate:UI:WithFilesBackup'|dict_s }}</span></div>
<div class="field_data">
<div class="field_value">{{ sItopArchive }}</div>
</div>
</div>
</div>
{% endif %}
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset id="installation_progress">
<legend>{{ 'iTopUpdate:UI:Progress'|dict_s }}</legend>
<div id="progress_content" style="height: 100px; overflow: auto; text-align: center;">
<p class="center">
<span id="setup_msg">{{ 'iTopUpdate:UI:SetupMessage:Ready'|dict_s }}</span>
</p>
<div id="progress" style="
margin: 5px auto;
display:block;
border:1px solid #000000;
width: 280px;
height: 20px;
line-height: 20px;
text-align: center;
">0%</div>
</div>
</fieldset>
<fieldset>
<div id="setup_error_outer" class="header_message message_error" style="display: none;">
<div>
<span id="setup_error"></span>
</div>
{% if bDoFilesArchive %}
<div>
<span>{{ 'iTopUpdate:UI:RestoreArchive'|dict_format(sItopArchive) }}</span>
</div>
{% endif %}
{% if bDoBackup %}
<div>
<span>{{ 'iTopUpdate:UI:RestoreBackup'|dict_format(sBackupFile) }}</span>
</div>
{% endif %}
<div>
<span>{{ 'iTopUpdate:UI:MaintenanceModeActive'|dict_s }}</span>
</div>
</div>
<form enctype="multipart/form-data" method="post">
<p><input id="setup_continue" type="submit" value="{{ 'iTopUpdate:UI:Continue'|dict_s }}" disabled/></p>
</form>
</fieldset>
</div>

View File

@@ -0,0 +1,122 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
function UpdateProgress(sMessage, iPercent) {
$('#setup_msg').html(sMessage);
$('#progress').progression({
Current: iPercent,
Maximum: 100,
aBackgroundImg: "{{ sProgressImage }}",
aTextColor: '#000000'
});
}
var oGetCurrentVersion = {
method: "POST",
url: "{{ sAjaxURL|raw }}",
data: {
"operation": "GetCurrentVersion"
},
dataType: "json",
success: function(data)
{
var oCurrentVersion = $("#current-version");
if (oCurrentVersion)
{
oCurrentVersion.html(data.sVersion);
}
}
}
function GetAjaxRequest(sOperation)
{
oAjaxRequest = {
method: "POST",
url: "{{ sAjaxURL|raw }}",
data: {
"operation": sOperation,
"authent": "{{ sSetupToken }}"
},
dataType: "json"
};
if (sOperation === "Backup")
{
oAjaxRequest.success = function() {
$("#do_backup_done").show();
};
}
if (sOperation === "FilesArchive")
{
oAjaxRequest.success = function() {
$("#do_files_archive_done").show();
};
}
return oAjaxRequest;
}
{% set aSteps = ['EnterMaintenance', 'Backup', 'FilesArchive', 'CopyFiles', 'Compile', 'ExitMaintenance', 'UpdateDone'] %}
aStepsName = [];
{% for sStep in aSteps %}
aStepsName.push({{ ('iTopUpdate:UI:SetupMessage:' ~ sStep)|dict_s|json_encode|raw }});
{% endfor %}
var sBackupStep;
{% if bDoBackup %}
sBackupStep = "Backup";
{% endif %}
var sFilesArchiveStep;
{% if bDoFilesArchive %}
sFilesArchiveStep = "FilesArchive";
{% endif %}
var aStepsAjaxOperation = ["EnterMaintenance", sBackupStep, sFilesArchiveStep, "CopyFiles", "Compile", "ExitMaintenance", null];
var iNextStep = 0;
function ExecNextStep() {
if (iNextStep < aStepsAjaxOperation.length)
{
var sAjaxOperation = aStepsAjaxOperation[iNextStep];
var iPercent = (iNextStep + 1) * 100 / aStepsAjaxOperation.length;
UpdateProgress(aStepsName[iNextStep], iPercent);
iNextStep++;
if (sAjaxOperation) {
$.ajax(GetAjaxRequest(sAjaxOperation))
.done(function () {
setTimeout(ExecNextStep, 500);
})
.fail(function ( jqXHR) {
$("#setup_continue").removeAttr("disabled");
if (jqXHR && jqXHR.responseJSON) {
$("#setup_error").html({{ 'iTopUpdate:Error:UpdateFailed'|dict_s|json_encode|raw }}+" "+jqXHR.responseJSON.sError);
}
else
{
$("#setup_error").html({{ 'iTopUpdate:Error:UpdateFailed'|dict_s|json_encode|raw }});
}
$('.progress').css("background-image", "none").css("background-color", "#fcc");
$("#setup_error_outer").show();
})
;
}
else
{
setTimeout(ExecNextStep, 500);
}
}
else
{
$.ajax(oGetCurrentVersion);
$("#setup_continue").removeAttr("disabled");
$("#current_version").removeClass("message_info").addClass("message_ok");
$('.progress').css("background-image", "none").css("background-color", "#cfc");
}
}
ExecNextStep();