diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index dc6d31b89..f8407dbca 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -4222,6 +4222,7 @@ EOF $oDummyObj->Set($sAttCode, $currValue); /** @var ormTagSet $oTagSet */ $oTagSet = $oDummyObj->Get($sAttCode); + $oTagSet->SetDisplayPartial(true); foreach($aKeys as $iIndex => $sValues) { if ($iIndex == 0) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 4193c6e9f..2ae9aa44d 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -9668,14 +9668,27 @@ class AttributeTagSet extends AttributeSet { $aJson['partial_values'] = array(); $aJson['orig_value'] = array(); + $aJson['added'] = array(); + $aJson['removed'] = array(); } else { - $aJson['partial_values'] = $oValue->GetModified(); $aJson['orig_value'] = array_merge($oValue->GetValues(), $oValue->GetModified()); + $aJson['added'] = $oValue->GetAdded(); + $aJson['removed'] = $oValue->GetRemoved(); + + if ($oValue->DisplayPartial()) + { + // For bulk updates + $aJson['partial_values'] = $oValue->GetModified(); + } + else + { + // For simple updates + $aJson['partial_values'] = array(); + } } - $aJson['added'] = array(); - $aJson['removed'] = array(); + $iMaxTags = $this->GetMaxItems(); $aJson['max_items_allowed'] = $iMaxTags; diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 71498c920..412b959a9 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1224,6 +1224,10 @@ abstract class DBObject implements iDisplay if ($this->InSyncScope()) { $iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons); + if ($iSynchroFlags & OPT_ATT_SLAVE) + { + $iSynchroFlags |= OPT_ATT_READONLY; + } } return $iFlags | $iSynchroFlags; // Combine both sets of flags } @@ -3869,7 +3873,7 @@ abstract class DBObject implements iDisplay foreach(MetaModel::ListAttributeDefs($sLinkClass) as $sAttCode => $oAttDef) { // As of now, ignore other attribute (do not attempt to recurse!) - if ($oAttDef->IsScalar()) + if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) { $oLinkClone->Set($sAttCode, $oSourceLink->Get($sAttCode)); } @@ -3932,7 +3936,7 @@ abstract class DBObject implements iDisplay $oObjectToRead = $aSourceObjects['source']; foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) { - if ($oAttDef->IsScalar()) + if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) { $this->CopyAttribute($oObjectToRead, $sAttCode, $sAttCode); } diff --git a/core/expressioncache.class.inc.php b/core/expressioncache.class.inc.php index 7d4161a13..eea5b79ce 100644 --- a/core/expressioncache.class.inc.php +++ b/core/expressioncache.class.inc.php @@ -77,11 +77,9 @@ EOF; } EOF; - file_put_contents($sFilePath, $content); - } + SetupUtils::builddir(dirname($sFilePath)); + file_put_contents($sFilePath, $content); } - // Restore original user language - Dict::SetUserLanguage($sUserLang); } static private function GetSerializedExpression($sClass, $sAttCode) diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php index 46dc7e04f..b8f9a1481 100644 --- a/core/ormtagset.class.inc.php +++ b/core/ormtagset.class.inc.php @@ -26,6 +26,9 @@ */ final class ormTagSet extends ormSet { + private $m_bDisplayPartial = false; + + /** * ormTagSet constructor. * @@ -299,6 +302,82 @@ final class ormTagSet extends ormSet return $aModifiedTagCodes; } + /** + * @return string[] list of codes for added entries + */ + public function GetAdded() + { + $aAddedTagCodes = array_keys($this->aAdded); + sort($aAddedTagCodes); + + return $aAddedTagCodes; + } + + /** + * @return string[] list of codes for removed entries + */ + public function GetRemoved() + { + $aRemovedTagCodes = array_keys($this->aRemoved); + sort($aRemovedTagCodes); + + return $aRemovedTagCodes; + } + + /** + * Apply a delta to the current ItemSet + * $aDelta['added] = array of added items + * $aDelta['removed'] = array of removed items + * + * @param $aDelta + * + * @throws \CoreException + */ + public function ApplyDelta($aDelta) + { + if (isset($aDelta['removed'])) + { + foreach($aDelta['removed'] as $oItem) + { + $this->Remove($oItem); + } + } + if (isset($aDelta['added'])) + { + foreach($aDelta['added'] as $oItem) + { + $this->Add($oItem); + } + } + } + + /** + * Populates the added and removed arrays for bulk edit + * + * @param string[] $aItems + * + * @throws \CoreException + */ + public function GenerateDiffFromArray($aItems) + { + foreach($this->GetValues() as $oCurrentItem) + { + if (!in_array($oCurrentItem, $aItems)) + { + $this->Remove($oCurrentItem); + } + } + + foreach($aItems as $oNewItem) + { + $this->Add($oNewItem); + } + + // Keep only the aModified list + $this->aRemoved = array(); + $this->aAdded = array(); + } + /** * Check whether a tag code is valid or not for this TagSet * @@ -479,4 +558,21 @@ final class ormTagSet extends ormSet return TagSetFieldData::GetTagDataClassName($this->sClass, $this->sAttCode); } + + /** + * @return bool + */ + public function DisplayPartial() + { + return $this->m_bDisplayPartial; + } + + /** + * @param bool $m_bDisplayPartial + */ + public function SetDisplayPartial($m_bDisplayPartial) + { + $this->m_bDisplayPartial = $m_bDisplayPartial; + } + } \ No newline at end of file diff --git a/datamodels/2.x/itop-backup/main.itop-backup.php b/datamodels/2.x/itop-backup/main.itop-backup.php index 44029672c..ed0a93b83 100644 --- a/datamodels/2.x/itop-backup/main.itop-backup.php +++ b/datamodels/2.x/itop-backup/main.itop-backup.php @@ -47,31 +47,14 @@ class DBBackupScheduled extends DBBackup { protected function LogInfo($sMsg) { - static $bDebug = null; - if ($bDebug == null) - { - $bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false); - } - - if ($bDebug) - { - echo $sMsg."\n"; - } + echo $sMsg."\n"; + IssueLog::Info($sMsg); } protected function LogError($sMsg) { - static $bDebug = null; - if ($bDebug == null) - { - $bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false); - } - IssueLog::Error($sMsg); - if ($bDebug) - { - echo 'Error: '.$sMsg."\n"; - } + echo 'Error: '.$sMsg."\n"; } /** diff --git a/datamodels/2.x/itop-backup/module.itop-backup.php b/datamodels/2.x/itop-backup/module.itop-backup.php index 9a43a3b16..e0db89476 100644 --- a/datamodels/2.x/itop-backup/module.itop-backup.php +++ b/datamodels/2.x/itop-backup/module.itop-backup.php @@ -52,7 +52,7 @@ SetupWebPage::AddModule( //'file_name_format' => '__DB__-%Y-%m-%d_%H_%M', 'retention_count' => 5, 'enabled' => true, - 'debug' => false, + 'itop_root' => '', 'itop_backup_incident' => '', ), ) diff --git a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml index 622756a15..4b765f69c 100755 --- a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml +++ b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml @@ -4561,7 +4561,7 @@ itop-change-mgmt-itil/images/change-ongoing.png - id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + id) AND (C.end_date < NOW() AND C.end_date > DATE_SUB(NOW(), INTERVAL 3 DAY ))]]> Tickets:Related:RecentChanges itop-change-mgmt-itil/images/change-done.png @@ -4579,7 +4579,7 @@ itop-change-mgmt-itil/images/change-ongoing.png - id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + id) AND (C.end_date < NOW() AND C.end_date > DATE_SUB(NOW(), INTERVAL 3 DAY ))]]> Tickets:Related:RecentChanges itop-change-mgmt-itil/images/change-done.png @@ -4597,7 +4597,7 @@ itop-change-mgmt-itil/images/change-ongoing.png - id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + id) AND (C.end_date < NOW() AND C.end_date > DATE_SUB(NOW(), INTERVAL 3 DAY ))]]> Tickets:Related:RecentChanges itop-change-mgmt-itil/images/change-done.png @@ -4619,7 +4619,7 @@ itop-change-mgmt-itil/images/change-ongoing.png - + DATE_SUB(NOW(), INTERVAL 3 DAY ))]]> Tickets:Related:RecentChanges itop-change-mgmt-itil/images/change-done.png @@ -4633,7 +4633,7 @@ itop-change-mgmt-itil/images/change-ongoing.png - + DATE_SUB(NOW(), INTERVAL 3 DAY ))]]> Tickets:Related:RecentChanges itop-change-mgmt-itil/images/change-done.png diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index dd811c5d8..f28d0c3e0 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -775,7 +775,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Title:DeletionOf_Object' => 'Deletion of %1$s', 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Bulk deletion of %1$d objects of class %2$s', 'UI:Delete:NotAllowedToDelete' => 'You are not allowed to delete this object', - 'UI:Delete:NotAllowedToUpdate_Fields' => 'You are not allowed to update the following field(s): %1$s', + 'UI:Error:ActionNotAllowed' => 'You are not allowed to do this action', 'UI:Error:NotEnoughRightsToDelete' => 'This object could not be deleted because the current user do not have sufficient rights', 'UI:Error:CannotDeleteBecause' => 'This object could not be deleted because: %1$s', 'UI:Error:CannotDeleteBecauseOfDepencies' => 'This object could not be deleted because some manual operations must be performed prior to that', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 3d4c86cdc..49acc5b77 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -758,6 +758,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Suppression massive de %1$d objets de type %2$s', 'UI:Delete:NotAllowedToDelete' => 'Vous n\'êtes pas autorisé à supprimer cet objet', 'UI:Delete:NotAllowedToUpdate_Fields' => 'Vous n\'êtes pas autorisé à mettre à jour les champs suivants : %1$s', + 'UI:Error:ActionNotAllowed' => 'Vous n\'êtes pas autorisé à effectuer cette action', 'UI:Error:NotEnoughRightsToDelete' => 'Cet objet ne peut pas être supprimé car l\'utilisateur courant n\'a pas les droits nécessaires.', 'UI:Error:CannotDeleteBecause' => 'Cet objet ne peut pas être effacé. Raison: %1$s', 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Cet objet ne peut pas être supprimé, des opérations manuelles sont nécessaire avant sa suppression.', diff --git a/lib/archivetar/tar.php b/lib/archivetar/tar.php index 050c22cfa..9337f7d57 100644 --- a/lib/archivetar/tar.php +++ b/lib/archivetar/tar.php @@ -1218,7 +1218,7 @@ class ArchiveTar $iBufferLen = strlen("$v_buffer"); if ($iBufferLen != $iLen) { - $iPack = ((int)($iBufferLen / 512) + 1) * 512; + $iPack = (ceil($iBufferLen / 512)) * 512; $sPack = sprintf('a%d', $iPack); } else diff --git a/pages/UI.php b/pages/UI.php index 3fcc41c10..4568aee3b 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1497,7 +1497,15 @@ EOF { throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); } - $oObj = MetaModel::GetObject($sClass, $id, false); + $aStimuli = MetaModel::EnumStimuli($sClass); + if ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) + { + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); + throw new ApplicationException(Dict::S('UI:Error:ActionNotAllowed')); + } + + $oObj = MetaModel::GetObject($sClass, $id, false); if ($oObj != null) { $aPrefillFormParam = array( 'user' => $_SESSION["auth_user"], @@ -1545,6 +1553,13 @@ EOF $sMessage = Dict::S('UI:Error:ObjectAlreadyUpdated'); $sSeverity = 'info'; } + elseif ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) + { + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); + $sMessage = Dict::S('UI:Error:ActionNotAllowed'); + $sSeverity = 'error'; + } else { $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 6dfbbd8a5..127873410 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -147,9 +147,11 @@ header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past $sOperation = Utils::ReadParam('operation', ''); try { - if (is_file(utils::GetConfigFilePath()) && !is_writable(utils::GetConfigFilePath())) + $sAuthent = utils::ReadParam('authent', '', false, 'raw_data'); + if (!file_exists(APPROOT.'data/setup/authent') || $sAuthent !== file_get_contents(APPROOT.'data/setup/authent')) { - throw new Exception('Setup operations are not allowed outside of the setup'); + throw new SecurityException('Setup operations are not allowed outside of the setup'); + SetupPage::log_error("Setup operations are not allowed outside of the setup"); } switch($sOperation) diff --git a/setup/backup.class.inc.php b/setup/backup.class.inc.php index 1eeb0254b..43753e530 100644 --- a/setup/backup.class.inc.php +++ b/setup/backup.class.inc.php @@ -313,8 +313,11 @@ if (class_exists('ZipArchive')) // The setup must be able to start even if the " $sTmpFolder = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax()); $aFiles = $this->PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder); + $sFilesList = var_export($aFiles, true); + $this->LogInfo("backup: adding to archive files '$sFilesList'"); $oArchive->createModify($aFiles, '', $sTmpFolder); + $this->LogInfo("backup: removing tmp folder '$sTmpFolder'"); SetupUtils::rrmdir($sTmpFolder); } @@ -334,6 +337,7 @@ if (class_exists('ZipArchive')) // The setup must be able to start even if the " { SetupUtils::rrmdir($sTmpFolder); } + $this->LogInfo("backup: creating tmp dir '$sTmpFolder'"); @mkdir($sTmpFolder, 0777, true); if (is_null($sSourceConfigFile)) { @@ -342,6 +346,7 @@ if (class_exists('ZipArchive')) // The setup must be able to start even if the " if (!empty($sSourceConfigFile)) { $sFile = $sTmpFolder.'/config-itop.php'; + $this->LogInfo("backup: adding resource '$sSourceConfigFile'"); copy($sSourceConfigFile, $sFile); $aRet[] = $sFile; } @@ -350,6 +355,7 @@ if (class_exists('ZipArchive')) // The setup must be able to start even if the " if (file_exists($sDeltaFile)) { $sFile = $sTmpFolder.'/delta.xml'; + $this->LogInfo("backup: adding resource '$sDeltaFile'"); copy($sDeltaFile, $sFile); $aRet[] = $sFile; } @@ -358,6 +364,7 @@ if (class_exists('ZipArchive')) // The setup must be able to start even if the " { $sModules = utils::GetCurrentEnvironment().'-modules'; $sFile = $sTmpFolder.'/'.$sModules; + $this->LogInfo("backup: adding resource '$sExtraDir'"); SetupUtils::copydir($sExtraDir, $sFile); $aRet[] = $sFile; } @@ -434,7 +441,7 @@ if (class_exists('ZipArchive')) // The setup must be able to start even if the " $sCommandDisplay = "$sMySQLDump --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx --password=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables"; // Now run the command for real - $this->LogInfo("Executing command: $sCommandDisplay"); + $this->LogInfo("backup: generate data file with command: $sCommandDisplay"); $aOutput = array(); $iRetCode = 0; exec($sCommand, $aOutput, $iRetCode); diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 3f53f9bf1..0716386dc 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -333,8 +333,9 @@ EOF; $aMenusToLoad[] = $sMenuId; } $aMenusToLoad = array_unique($aMenusToLoad); - $aMenusForAll = array(); - $aMenusForAdmins = array(); + $aMenuLinesForAll = array(); + $aMenuLinesForAdmins = array(); + $aAdminMenus = array(); foreach($aMenusToLoad as $sMenuId) { $oMenuNode = $aMenuNodes[$sMenuId]; @@ -365,25 +366,27 @@ EOF; { throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage()); } - if ($oMenuNode->GetChildText('enable_admin_only') == '1') + $sParent = $oMenuNode->GetChildText('parent', null); + if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent])) { - $aMenusForAdmins = array_merge($aMenusForAdmins, $aMenuLines); + $aMenuLinesForAdmins = array_merge($aMenuLinesForAdmins, $aMenuLines); + $aAdminMenus[$oMenuNode->getAttribute("id")] = true; } else { - $aMenusForAll = array_merge($aMenusForAll, $aMenuLines); + $aMenuLinesForAll = array_merge($aMenuLinesForAll, $aMenuLines); } } $sIndent = "\t\t"; - foreach ($aMenusForAll as $sPHPLine) + foreach ($aMenuLinesForAll as $sPHPLine) { $sCompiledCode .= $sIndent.$sPHPLine."\n"; } - if (count($aMenusForAdmins) > 0) + if (count($aMenuLinesForAdmins) > 0) { $sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n"; $sCompiledCode .= $sIndent."{\n"; - foreach ($aMenusForAdmins as $sPHPLine) + foreach ($aMenuLinesForAdmins as $sPHPLine) { $sCompiledCode .= $sIndent."\t".$sPHPLine."\n"; } diff --git a/setup/setup.js b/setup/setup.js index 2474d0fa3..88a7cba8f 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -2,8 +2,9 @@ function WizardAsyncAction(sActionCode, oParams, OnErrorFunction) { var sStepClass = $('#_class').val(); var sStepState = $('#_state').val(); + var sAuthent = $('#authent_token').val(); - var oMap = { operation: 'async_action', step_class: sStepClass, step_state: sStepState, code: sActionCode, params: oParams }; + var oMap = { operation: 'async_action', step_class: sStepClass, step_state: sStepState, code: sActionCode, authent : sAuthent, params: oParams }; var ErrorFn = OnErrorFunction; $(document).ajaxError(function(event, request, settings) { diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php index 91631d78f..1e3798c9c 100644 --- a/setup/wizardsteps.class.inc.php +++ b/setup/wizardsteps.class.inc.php @@ -57,6 +57,13 @@ class WizStepWelcome extends WizardStep public function ProcessParams($bMoveForward = true) { + if (!file_exists(APPROOT.'data/setup')) + { + mkdir(APPROOT.'data/setup'); + } + $sUID = hash('sha256', rand()); + file_put_contents(APPROOT.'data/setup/authent', $sUID); + $this->oWizard->SetParameter('authent', $sUID); return array('class' => 'WizStepInstallOrUpgrade', 'state' => ''); } @@ -284,6 +291,8 @@ class WizStepInstallOrUpgrade extends WizardStep $oPage->add(''); $oPage->add($sMySQLDumpMessage.'
'.$sMessage.''); $oPage->add(''); + $sAuthentToken = $this->oWizard->GetParameter('authent', ''); + $oPage->add(''); //$oPage->add(''); $oPage->add_ready_script( <<add(''); SetupUtils::DisplayDBParameters($oPage, true, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTlsEnabled, $sTlsCA, $sNewDBName); + $sAuthentToken = $this->oWizard->GetParameter('authent', ''); + $oPage->add(''); $oPage->add('
'); $sCreateDB = $this->oWizard->GetParameter('create_db', 'yes'); if ($sCreateDB == 'no') @@ -996,6 +1007,8 @@ class WizStepMiscParams extends WizardStep $sChecked = ($sSampleData == 'no') ? 'checked ' : ''; $oPage->p('