* @since 3.1.0 * @package Combodo\iTop\Controller\Base\Layout */ class ObjectController extends AbstractController { public const ROUTE_NAMESPACE = 'object'; /** * @return \iTopWebPage|\AjaxPage Object edit form in its webpage * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \CoreException * @throws \SecurityException */ public function OperationModify() { $bPrintable = utils::ReadParam('printable', '0') === '1'; $sClass = utils::ReadParam('class', '', false, 'class'); $sId = utils::ReadParam('id', ''); // Check parameters if (utils::IsNullOrEmptyString($sClass) || utils::IsNullOrEmptyString($sId)) { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } $oObj = MetaModel::GetObject($sClass, $sId, false); // Check user permissions // - Is allowed to view it? if (is_null($oObj)) { throw new ApplicationException(Dict::S('UI:ObjectDoesNotExist')); } // - Is allowed to edit it? $oSet = CMDBObjectSet::FromObject($oObj); if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_NO) { throw new SecurityException('User not allowed to modify this object', array('class' => $sClass, 'id' => $sId)); } // Prepare web page (should more likely be some kind of response object like for Symfony) $aFormExtraParams = array('wizard_container' => 1); if ($this->IsHandlingXmlHttpRequest()) { $oPage = new AjaxPage(''); $aFormExtraParams['js_handlers'] = []; $aFormExtraParams['noRelations'] = true; // We display this form in a modal, once we submit (in ajax) we probably want to only close the modal $aFormExtraParams['js_handlers']['form_on_submit'] = <<DisableBreadCrumb(); $oPage->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObj, cmdbAbstractObject::ENUM_DISPLAY_MODE_EDIT)); } // - JS files foreach (static::EnumRequiredForModificationJsFilesRelPaths() as $sJsFileRelPath) { $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sJsFileRelPath); } // Note: Code duplicated to the case 'apply_modify' in UI.php when a data integrity issue has been found $oObj->DisplayModifyForm($oPage, $aFormExtraParams); // wizard_container: Display the title above the form return $oPage; } /** * @return \iTopWebPage|\JsonPage Object edit form in its webpage * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \CoreException * @throws \SecurityException */ public function OperationApplyNew() { $bPrintable = utils::ReadParam('printable', '0') === '1'; $aResult = []; if ($this->IsHandlingXmlHttpRequest()) { $oPage = new JsonPage(); $oPage->SetOutputDataOnly(true); $aResult['success'] = false; } else { $oPage = new iTopWebPage('', $bPrintable); $oPage->DisableBreadCrumb(); } $sClass = utils::ReadPostedParam('class', '', 'class'); $sClassLabel = MetaModel::GetName($sClass); $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); $aErrors = array(); $aWarnings = array(); if ( empty($sClass) ) { IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not created (empty class)', $sClass, array( '$sTransactionId' => $sTransactionId, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } if (!utils::IsTransactionValid($sTransactionId, false)) { $sUser = UserRights::GetUser(); IssueLog::Error(__CLASS__.'::'.__METHOD__." : invalid transaction_id ! data: user='$sUser', class='$sClass'"); if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::S('UI:Error:ObjectAlreadyCreated')]; } else { $oErrorAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:Error:ObjectAlreadyCreated')); $oErrorAlert->SetIsClosable(false) ->SetIsCollapsible(false); $oPage->AddUiBlock($oErrorAlert); } IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not created (invalid transaction_id)', $sClass, array( '$sTransactionId' => $sTransactionId, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); } else { $oObj = MetaModel::NewObject($sClass); if (MetaModel::HasLifecycle($sClass)) { $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); $sTargetState = utils::ReadPostedParam('obj_state', ''); if ($sTargetState != '') { $sOrigState = utils::ReadPostedParam('obj_state_orig', ''); if ($sTargetState != $sOrigState) { $aWarnings[] = Dict::S('UI:StateChanged'); } $oObj->Set($sStateAttCode, $sTargetState); } } $aErrors = $oObj->UpdateObjectFromPostedForm(); } if (isset($oObj) && is_object($oObj)) { $sClass = get_class($oObj); $sClassLabel = MetaModel::GetName($sClass); try { if (!empty($aErrors) || !empty($aWarnings)) { IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not created (see $aErrors)', $sClass, array( '$sTransactionId' => $sTransactionId, '$aErrors' => $aErrors, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); } $oObj->DBInsertNoReload();// No need to reload IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object created', $sClass, array( '$id' => $oObj->GetKey(), '$sTransactionId' => $sTransactionId, '$aErrors' => $aErrors, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); utils::RemoveTransaction($sTransactionId); $oPage->set_title(Dict::S('UI:PageTitle:ObjectCreated')); QuickCreateHelper::AddClassToHistory($sClass); // Compute the name, by reloading the object, even if it disappeared from the silo $oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/); $sName = $oObj->GetName(); $sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel); $sNextAction = utils::ReadPostedParam('next_action', ''); if (!empty($sNextAction)) { $oPage->add("

$sMessage

"); try { ApplyNextAction($oPage, $oObj, $sNextAction); } catch (ApplicationException $e) { $sMessage = $e->getMessage(); $sSeverity = 'info'; ReloadAndDisplay($oPage, $oObj, 'create', $sMessage, $sSeverity); } } else { // Nothing more to do if ($this->IsHandlingXmlHttpRequest()) { $aResult['success'] = true; } else { ReloadAndDisplay($oPage, $oObj, 'create', $sMessage, 'ok'); } } } catch (CoreCannotSaveObjectException $e) { // Found issues, explain and give the user a second chance // $aIssues = $e->getIssues(); if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => $e->getHtmlMessage()]; } else { $sObjKey = $oObj->GetKey(); $sClassIcon = MetaModel::GetClassIcon($sClass, false); $sHeaderTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel); $oPage->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); if (!empty($aIssues)) { $oPage->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); } if (!empty($aWarnings)) { $sWarnings = implode(', ', $aWarnings); $oPage->AddHeaderMessage($sWarnings, 'message_warning'); } cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, [], ['transaction_id' => $sTransactionId]); } } } if ($this->IsHandlingXmlHttpRequest()) { $oPage->SetData($aResult); } return $oPage; } /** * @return \iTopWebPage|\JsonPage * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \ConfigException * @throws \CoreException * @throws \CoreUnexpectedValue * @throws \DictExceptionMissingString * @throws \MySQLException */ public function OperationApplyModify(){ $bPrintable = utils::ReadParam('printable', '0') === '1'; $aResult = []; if ($this->IsHandlingXmlHttpRequest()) { $oPage = new JsonPage(); $oPage->SetOutputDataOnly(true); $aResult['success'] = false; } else { $oPage = new iTopWebPage('', $bPrintable); $oPage->DisableBreadCrumb(); } $sClass = utils::ReadPostedParam('class', '', 'class'); $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadPostedParam('id', ''); $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); if ( empty($sClass) || empty($id)) { IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not updated (empty class or id)', $sClass, array( '$id' => $id, '$sTransactionId' => $sTransactionId, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); // TODO 3.1 Do not crash with an exception in ajax throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } $bDisplayDetails = true; $oObj = MetaModel::GetObject($sClass, $id, false); if ($oObj === null) { $bDisplayDetails = false; if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::S('UI:ObjectDoesNotExist')]; } else { $oPage->set_title(Dict::S('UI:ErrorPageTitle')); $oErrorAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:ObjectDoesNotExist')); $oErrorAlert->SetIsClosable(false) ->SetIsCollapsible(false); $oPage->AddUiBlock($oErrorAlert); } IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not updated (id not found)', $sClass, array( '$id' => $id, '$sTransactionId' => $sTransactionId, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); } elseif (!utils::IsTransactionValid($sTransactionId, false)) { //TODO: since $bDisplayDetails= true, there will be an redirection, thus, the content generated here is ignored, only the $sMessage and $sSeverity are used after the redirection $sUser = UserRights::GetUser(); IssueLog::Error(__CLASS__.'::'.__METHOD__." : invalid transaction_id ! data: user='$sUser', class='$sClass'"); if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::S('UI:Error:ObjectAlreadyUpdated')]; } else { $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding $oPage->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); } $sMessage = Dict::Format('UI:Error:ObjectAlreadyUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); $sSeverity = 'error'; IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not updated (invalid transaction_id)', $sClass, array( '$id' => $id, '$sTransactionId' => $sTransactionId, '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); } else { $aErrors = $oObj->UpdateObjectFromPostedForm(); $sMessage = ''; $sSeverity = 'ok'; if (!$oObj->IsModified() && empty($aErrors)) { if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())]; } else { $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding } $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); $sSeverity = 'info'; IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not updated (see either $aErrors or IsModified)', $sClass, array( '$id' => $id, '$sTransactionId' => $sTransactionId, '$aErrors' => $aErrors, 'IsModified' => $oObj->IsModified(), '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); } else { IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object updated', $sClass, array( '$id' => $id, '$sTransactionId' => $sTransactionId, '$aErrors' => $aErrors, 'IsModified' => $oObj->IsModified(), '$sUser' => UserRights::GetUser(), 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); try { if (!empty($aErrors)) { throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); } // Transactions are now handled in DBUpdate $oObj->DBUpdate(); $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); $sSeverity = 'ok'; if ($this->IsHandlingXmlHttpRequest()) { $aResult['success'] = true; } } catch (CoreCannotSaveObjectException $e) { // Found issues, explain and give the user a second chance // $bDisplayDetails = false; $aIssues = $e->getIssues(); if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => $e->getHtmlMessage()]; } else { $oPage->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); $oObj->DisplayModifyForm($oPage, array('wizard_container' => true)); // wizard_container: display the wizard border and the title } } catch (DeleteException $e) { if ($this->IsHandlingXmlHttpRequest()) { $aResult['data'] = ['error_message' => Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())]; } else { // Say two things: // - 1) Don't be afraid nothing was modified $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); $sSeverity = 'info'; cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, $sSeverity, 0, true /* must not exist */); // - 2) Ok, there was some trouble indeed $sMessage = $e->getMessage(); $sSeverity = 'error'; utils::RemoveTransaction($sTransactionId); } } } } if ($bDisplayDetails) { $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly $sNextAction = utils::ReadPostedParam('next_action', ''); if (!empty($sNextAction)) { try { ApplyNextAction($oPage, $oObj, $sNextAction); } catch (ApplicationException $e) { $sMessage = $e->getMessage(); $sSeverity = 'info'; ReloadAndDisplay($oPage, $oObj, 'update', $sMessage, $sSeverity); } } else { // Nothing more to do $sMessage = isset($sMessage) ? $sMessage : ''; $sSeverity = isset($sSeverity) ? $sSeverity : null; if ($this->IsHandlingXmlHttpRequest()) { ; } else{ ReloadAndDisplay($oPage, $oObj, 'update', $sMessage, $sSeverity); } } $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); if ($bLockEnabled) { // Release the concurrent lock, if any $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); if ($sOwnershipToken !== null) { // We're done, let's release the lock iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); } } } if ($this->IsHandlingXmlHttpRequest()) { $oPage->SetData($aResult); } return $oPage; } /** * @return string[] Rel. paths (to iTop root folder) of required JS files for object modification (create, edit, stimulus, ...) */ public static function EnumRequiredForModificationJsFilesRelPaths(): array { return [ 'js/json.js', 'js/forms-json-utils.js', 'js/wizardhelper.js', 'js/wizard.utils.js', 'js/linkswidget.js', 'js/linksdirectwidget.js', 'js/extkeywidget.js', 'js/jquery.blockUI.js', ]; } }