diff --git a/.gitignore b/.gitignore index cb2d93dd1..462770aad 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ test/vendor/* !/data/.htaccess !/data/index.php !/data/web.config +!/data/exclude.txt +!/data/.compilation-symlinks # iTop extensions /extensions/** diff --git a/data/.compilation-symlinks b/data/.compilation-symlinks new file mode 100644 index 000000000..e69de29bb diff --git a/data/exclude.txt b/data/exclude.txt new file mode 100644 index 000000000..e594eaea8 --- /dev/null +++ b/data/exclude.txt @@ -0,0 +1,5 @@ +# +# The following source files are not re-distributed with the "build" of the application +# since they are used solely for debugging. The minified version is normally used instead. +# +.compilation-symlinks diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 6253d2abe..cc9ad9fa7 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -164,23 +164,27 @@ try /** @var WizardStep $oStep */ $oStep = new $sClass($oDummyController, $sState); $sConfigFile = utils::GetConfigFilePath(); - if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig()) - { + if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig()) { $sRelativePath = utils::GetConfigFilePathRelative(); $oPage->error("Error: the configuration file '".$sRelativePath."' already exists and cannot be overwritten."); $oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '".$sRelativePath."' can be modified by the web server."); $oPage->output(); - } - else - { + } else { $oStep->AsyncAction($oPage, $sActionCode, $aParams); } } - $oPage->output(); - break; + $oPage->output(); + break; + + case 'toggle_use_symbolic_links': + $sUseSymbolicLinks = Utils::ReadParam('bUseSymbolicLinks', false); + $bUseSymbolicLinks = ($sUseSymbolicLinks === 'true'); + MFCompiler::SetUseSymbolicLinksFlag($bUseSymbolicLinks); + echo "toggle useSymbolicLInks file : $bUseSymbolicLinks"; + break; default: - throw(new Exception("Error unsupported operation '$sOperation'")); + throw(new Exception("Error unsupported operation '$sOperation'")); } } catch(Exception $e) diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index 5b7d4a869..38894c92f 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -274,17 +274,14 @@ class ApplicationInstaller $sExtensionDir = $this->oParams->Get('extensions_dir', 'extensions'); $sTargetEnvironment = $this->GetTargetEnv(); $sTargetDir = $this->GetTargetDir(); - $bUseSymbolicLinks = false; $aMiscOptions = $this->oParams->Get('options', array()); - if (isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks']) - { - if (function_exists('symlink')) - { + + $bUseSymbolicLinks = null; + if ((isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks'])) { + if (function_exists('symlink')) { $bUseSymbolicLinks = true; SetupLog::Info("Using symbolic links instead of copying data model files (for developers only!)"); - } - else - { + } else { SetupLog::Info("Symbolic links (function symlinks) does not seem to be supported on this platform (OS/PHP version)."); } } @@ -504,8 +501,7 @@ class ApplicationInstaller { $oBackup = new SetupDBBackup($oConfig); $sTargetFile = $oBackup->MakeName($sBackupFileFormat); - if (!empty($sMySQLBinDir)) - { + if (!empty($sMySQLBinDir)) { $oBackup->SetMySQLBinDir($sMySQLBinDir); } @@ -513,8 +509,8 @@ class ApplicationInstaller $oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile); } - - protected static function DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sEnvironment, $bUseSymbolicLinks = false) + + protected static function DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sEnvironment, $bUseSymbolicLinks = null) { SetupLog::Info("Compiling data model."); @@ -522,8 +518,7 @@ class ApplicationInstaller require_once(APPROOT.'setup/modelfactory.class.inc.php'); require_once(APPROOT.'setup/compiler.class.inc.php'); - if (empty($sSourceDir) || empty($sTargetDir)) - { + if (empty($sSourceDir) || empty($sTargetDir)) { throw new Exception("missing parameter source_dir and/or target_dir"); } diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 82b542696..21e284cd1 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -51,7 +51,15 @@ class DOMFormatException extends Exception */ class MFCompiler { - const DATA_PRECOMPILED_FOLDER = 'data' . DIRECTORY_SEPARATOR . 'precompiled_styles' . DIRECTORY_SEPARATOR; + const DATA_PRECOMPILED_FOLDER = 'data'.DIRECTORY_SEPARATOR.'precompiled_styles'.DIRECTORY_SEPARATOR; + + /** + * Path to the "use symlinks" file + * If this file is present, then we will compile to symlink ! + * + * @var string + */ + public const USE_SYMBOLIC_LINKS_FILE_PATH = APPROOT.'data/.compilation-symlinks'; /** @var \ThemeHandlerService */ protected static $oThemeHandlerService; @@ -93,54 +101,112 @@ class MFCompiler protected function DumpLog($oPage) { - foreach ($this->aLog as $sText) - { + foreach ($this->aLog as $sText) { $oPage->p($sText); } } - + public function GetLog() { return $this->aLog; } - + + /** + * @param bool $bForce if true then will just check file existence + * + * @return bool possible return values : + * * always false if not in dev env + * * `symlink` function non existent : false + * * if flag is present true, false otherwise + * + * @uses utils::IsDevelopmentEnvironment() + * @uses \function_exists() + * @uses \file_exists() + * @uses USE_SYMBOLIC_LINKS_FILE_PATH + * + * @since 3.0.0 + */ + public static function IsUseSymbolicLinksFlagPresent(bool $bForce = false): bool + { + if (!$bForce) { + if (!utils::IsDevelopmentEnvironment()) { + return false; + } + + if (!function_exists('symlink')) { + return false; + } + } + + return (file_exists(static::USE_SYMBOLIC_LINKS_FILE_PATH)); + } + + /** + * @param bool $bUseSymbolicLinks + * + * @since 3.0.0 method creation + * @uses USE_SYMBOLIC_LINKS_FILE_PATH + */ + public static function SetUseSymbolicLinksFlag(bool $bUseSymbolicLinks): void + { + $bHasUseSymlinksFile = static::IsUseSymbolicLinksFlagPresent(true); + + if ($bUseSymbolicLinks) { + if ($bHasUseSymlinksFile) { + return; + } + + touch(static::USE_SYMBOLIC_LINKS_FILE_PATH); + + return; + } + + if (!$bHasUseSymlinksFile) { + return; + } + unlink(static::USE_SYMBOLIC_LINKS_FILE_PATH); + } /** * Compile the data model into PHP files and data structures + * * @param string $sTargetDir The target directory where to put the resulting files * @param Page $oP For some output... * @param bool $bUseSymbolicLinks * @param bool $bSkipTempDir - * @throws Exception + * * @return void + * @throws Exception */ - public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = false, $bSkipTempDir = false) + public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = null, $bSkipTempDir = false) { + if (is_null($bUseSymbolicLinks)) { + $bUseSymbolicLinks = false; + if (self::IsUseSymbolicLinksFlagPresent()) { + // We are only overriding the useSymLinks option if the consumer didn't specify anything + // The toolkit always send this parameter for example, but not the Designer Connector + $bUseSymbolicLinks = true; + } + } + $sFinalTargetDir = $sTargetDir; $bIsAlreadyInMaintenanceMode = SetupUtils::IsInMaintenanceMode(); $sConfigFilePath = utils::GetConfigFilePath($this->sEnvironment); - if (is_file($sConfigFilePath)) - { + if (is_file($sConfigFilePath)) { $oConfig = new Config($sConfigFilePath); - } - else - { + } else { $oConfig = null; } - if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) - { + if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) { SetupUtils::EnterMaintenanceMode($oConfig); } - if ($bUseSymbolicLinks || $bSkipTempDir) - { + if ($bUseSymbolicLinks || $bSkipTempDir) { // Skip the creation of a temporary dictionary, not compatible with symbolic links $sTempTargetDir = $sFinalTargetDir; SetupUtils::rrmdir($sFinalTargetDir); SetupUtils::builddir($sFinalTargetDir); // Here is the directory - } - else - { + } else { // Create a temporary directory // Once the compilation is 100% successful, then move the results into the target directory $sTempTargetDir = tempnam(SetupUtils::GetTmpDir(), 'itop-'); @@ -226,17 +292,14 @@ class MFCompiler // The bullet proof implementation would be to compile in a separate directory as it has been done with the dictionaries... that's another story $aModules = $this->oFactory->GetLoadedModules(); $sUserRightsModule = ''; - foreach($aModules as $foo => $oModule) - { - if ($oModule->GetName() == 'itop-profiles-itil') - { + foreach ($aModules as $foo => $oModule) { + if ($oModule->GetName() == 'itop-profiles-itil') { $sUserRightsModule = 'itop-profiles-itil'; break; } } $oUserRightsNode = $this->oFactory->GetNodes('user_rights')->item(0); - if ($oUserRightsNode && ($sUserRightsModule == '')) - { + if ($oUserRightsNode && ($sUserRightsModule == '')) { // Legacy algorithm (itop <= 2.0.3) $sUserRightsModule = $oUserRightsNode->getAttribute('_created_in'); } @@ -245,13 +308,12 @@ class MFCompiler // List root classes // $this->aRootClasses = array(); - foreach ($this->oFactory->ListRootClasses() as $oClass) - { + foreach ($this->oFactory->ListRootClasses() as $oClass) { $this->Log("Root class (with child classes): ".$oClass->getAttribute('id')); $this->aRootClasses[$oClass->getAttribute('id')] = $oClass; } - - $this->LoadSnippets(); + + $this->LoadSnippets(); // Compile, module by module // @@ -264,33 +326,25 @@ class MFCompiler $this->WriteStaticOnlyHtaccess($sTempTargetDir); $this->WriteStaticOnlyWebConfig($sTempTargetDir); - foreach($aModules as $foo => $oModule) - { + static::SetUseSymbolicLinksFlag($bUseSymbolicLinks); + + foreach ($aModules as $foo => $oModule) { $sModuleName = $oModule->GetName(); $sModuleVersion = $oModule->GetVersion(); $sModuleRootDir = $oModule->GetRootDir(); - if ($sModuleRootDir != '') - { + if ($sModuleRootDir != '') { $sModuleRootDir = realpath($sModuleRootDir); $sRelativeDir = basename($sModuleRootDir); - if ($bUseSymbolicLinks) - { + if ($bUseSymbolicLinks) { $sRealRelativeDir = substr($sModuleRootDir, $iStart); - } - else - { + } else { $sRealRelativeDir = $sRelFinalTargetDir.'/'.$sRelativeDir; } // Push the other module files SetupUtils::copydir($sModuleRootDir, $sTempTargetDir.'/'.$sRelativeDir, $bUseSymbolicLinks); - - - - } - else - { + } else { $sRelativeDir = $sModuleName; $sRealRelativeDir = $sModuleName; } diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 30535086e..5f9023857 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -490,11 +490,13 @@ class RunTimeEnvironment * The list of modules to be installed in the target environment is: * - the list of modules present in the "source_dir" (defined by the source environment) which are marked as "installed" in the source environment's database * - plus the list of modules present in the "extra" directory of the target environment: data/-modules/ + * * @param string $sSourceEnv The name of the source environment to 'imitate' * @param bool $bUseSymLinks Whether to create symbolic links instead of copies + * * @return string[] */ - public function CompileFrom($sSourceEnv, $bUseSymLinks = false) + public function CompileFrom($sSourceEnv, $bUseSymLinks = null) { $oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv)); $sSourceDir = $oSourceConfig->Get('source_dir'); @@ -504,7 +506,7 @@ class RunTimeEnvironment // $oFactory = new ModelFactory($sSourceDirFull); $aModulesToCompile = $this->GetMFModulesToCompile($sSourceEnv, $sSourceDir); - foreach($aModulesToCompile as $oModule) + foreach ($aModulesToCompile as $oModule) { if ($oModule instanceof MFDeltaModule) { @@ -514,15 +516,12 @@ class RunTimeEnvironment } $oFactory->LoadModule($oModule); } - - if ($oModule instanceof MFDeltaModule) - { + + if ($oModule instanceof MFDeltaModule) { // A delta was loaded, let's save a second copy of the datamodel $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'-with-delta.xml'); - } - else - { + } else { // No delta was loaded, let's save the datamodel now $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml'); } diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php index f6f5d552a..8c50b8b27 100644 --- a/setup/wizardsteps.class.inc.php +++ b/setup/wizardsteps.class.inc.php @@ -1161,6 +1161,30 @@ class WizStepUpgradeMiscParams extends WizardStep }); EOF ); + + if (MFCompiler::IsUseSymbolicLinksFlagPresent()) { + $oPage->add('
'); + $oPage->add('Dev parameters'); + $oPage->p('
'); + $oPage->add_ready_script(<<<'JS' +$("#use-symbolic-links").on("click", function() { + var $this = $(this), + bUseSymbolicLinks = $this.prop("checked"); + if (!bUseSymbolicLinks){ + if (!window.confirm("This will disable symbolic links generation.\nYou'll need the toolkit to restore this option.\n\nAre you sure ?")) { + $this.prop("checked", true); + return; + } + } + + var sAuthent = $('#authent_token').val(); + var oAjaxParams = { operation: 'toggle_use_symbolic_links', bUseSymbolicLinks: bUseSymbolicLinks, authent: sAuthent}; + $.post(GetAbsoluteUrlAppRoot()+'setup/ajax.dataloader.php', oAjaxParams); +}); +JS + ); + } } public function AsyncAction(WebPage $oPage, $sCode, $aParameters)