Files
iTop/sources/Controller/Links/LinksetController.php
Stephen Abello c1922e2b3f N°3300 - Add creation and modification of 1-n objects in object details (#385)
* Rebase onto develop

* Use exit condition instead of englobing condition

* Add informative modals that can be called from modal toolbox

* Refactor "apply_modify" and "apply_new" into own controller, handle ajax requests with a json response and handle these responses in linkset creation/edition

* Fix merge issues

* Remove inverted condition

* Move linkset create button to a better place, still needs to fix duplicate "New" button caused by a refactor

* Handle "Cancel" button in modals

* Do not display relations when editing an object in a modal

* More elegant way to add "New" button to relations lists

* Factorize vertical highlights in alerts and modal in a single mixin

* Replace button name with dict entry code

* Change route name to snake case

* More elegant way to add "Create in modal" button to relations lists

* Replace triple if with in_array

* Move listener to body

* Rename variable to match boolean rules

* Rename event

* Rename extra param

* Add phpdoc

* Revert changes

* Check indirect linkset rights before allowing creation in modal

* Allow to modify linked object from 1-n relation in modal

* Add "New" button to 1-n relations

* Fix "new" button on 1-n

* Add todo

* Handle multiple classes choice

* Rework multiple classes choice and only allow LinksetController to be called from an ajax context

* PhpDoc

* Update sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-01-18 15:43:31 +01:00

240 lines
7.4 KiB
PHP

<?php
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Controller\Links;
use AjaxPage;
use cmdbAbstractObject;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Controller\AbstractController;
use CoreException;
use DBObject;
use JsonPage;
use MetaModel;
use UserRights;
use utils;
/**
* Class LinkSetController
*
* @internal
* @since 3.1.0
* @package Combodo\iTop\Controller
*/
class LinkSetController extends AbstractController
{
public const ROUTE_NAMESPACE = 'linkset';
/**
* OperationDeleteLinkedObject.
*
* @return JsonPage
* @throws \CoreException
*/
public function OperationDeleteLinkedObject(): JsonPage
{
if (!$this->IsHandlingXmlHttpRequest()) {
throw new CoreException('LinksetController can only be called in ajax.');
}
$oPage = new JsonPage();
$sErrorMessage = null;
$bOperationSuccess = false;
// retrieve parameters
$sLinkedObjectClass = utils::ReadParam('linked_object_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS);
$sLinkedObjectObjectKey = utils::ReadParam('linked_object_key', 0, false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sTransactionId = utils::ReadParam('transaction_id', null, false, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID);
// check transaction id
if (utils::IsTransactionValid($sTransactionId, false)) {
try {
$oDeletionPlan = MetaModel::GetObject($sLinkedObjectClass, $sLinkedObjectObjectKey)->DBDelete();
$bOperationSuccess = (count($oDeletionPlan->GetIssues()) === 0);
if (!$bOperationSuccess) {
$sErrorMessage = json_encode($oDeletionPlan->GetIssues());
}
}
catch (\Exception $e) {
$sErrorMessage = $e->getMessage();
}
} else {
$sErrorMessage = 'invalid transaction id';
}
$oPage->SetData([
'success' => $bOperationSuccess,
'error_message' => $sErrorMessage,
]);
return $oPage;
}
/**
* OperationDetachLinkedObject.
*
* @return \JsonPage
* @throws \CoreException
*/
public function OperationDetachLinkedObject(): JsonPage
{
if (!$this->IsHandlingXmlHttpRequest()) {
throw new CoreException('LinksetController can only be called in ajax.');
}
$oPage = new JsonPage();
$sErrorMessage = null;
$bOperationSuccess = false;
// retrieve parameters
$sLinkedObjectClass = utils::ReadParam('linked_object_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS);
$sLinkedObjectKey = utils::ReadParam('linked_object_key', 0, false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sExternalKeyAttCode = utils::ReadParam('external_key_att_code', null, false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sTransactionId = utils::ReadParam('transaction_id', null, false, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID);
// check transaction id
if (utils::IsTransactionValid($sTransactionId, false)) {
try {
$oLinkedObject = MetaModel::GetObject($sLinkedObjectClass, $sLinkedObjectKey);
$oLinkedObject->Set($sExternalKeyAttCode, null);
$oLinkedObject->DBWrite();
$bOperationSuccess = true;
}
catch (\Exception $e) {
$sErrorMessage = $e->getMessage();
}
} else {
$sErrorMessage = 'invalid transaction id';
}
$oPage->SetData([
'success' => $bOperationSuccess,
'error_message' => $sErrorMessage,
]);
return $oPage;
}
/**
* @return \AjaxPage Create edit form in its webpage
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \SecurityException
*/
public function OperationCreateLinkedObject()
{
if (!$this->IsHandlingXmlHttpRequest()) {
throw new CoreException('LinksetController can only be called in ajax.');
}
$oPage = new AjaxPage('');
$sProposedRealClass = utils::ReadParam('class', '', false, 'class');
$sAttCode = utils::ReadParam('att_code', '', false, 'raw');
$sClass = utils::ReadParam('host_class', '', false, 'class');
$sId = utils::ReadParam('host_id', '', false, 'integer');
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
// and that the current user is allowed to create objects of this class
$sRealClass = '';
$aSubClasses = MetaModel::EnumChildClasses($sProposedRealClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$aPossibleClasses = array();
foreach ($aSubClasses as $sCandidateClass) {
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) {
if ($sCandidateClass == $sProposedRealClass) {
$sRealClass = $sProposedRealClass;
}
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
}
}
// Only one of the subclasses can be instantiated...
if (count($aPossibleClasses) == 1) {
$aKeys = array_keys($aPossibleClasses);
$sRealClass = $aKeys[0];
}
if ($sRealClass != '') {
$oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$aFieldFlags = array(); // TODO 3.1 array($sExtKeyToMe => OPT_ATT_READONLY);
$oObj = DBObject::MakeDefaultInstance($sRealClass);
$oSourceObj = MetaModel::GetObject($sClass, $sId);
$oObj->Set($sExtKeyToMe, $sId);
$aPrefillParam = array('source_obj' => $oSourceObj);
$oObj->PrefillForm('creation_from_editinplace', $aPrefillParam);
// We display this form in a modal, once we submit (in ajax) we probably want to only close the modal
$sFormOnSubmitJsCode =
<<<JS
event.preventDefault();
if(bOnSubmitForm === true)
{
let oForm = $(this);
let sUrl = oForm.attr('action');
let sPosting = $.post( sUrl, oForm.serialize());
/* Alerts the results */
sPosting.done(function(data) {
if(data.success !== undefined && data.success === true) {
oForm.closest('[data-role="ibo-modal"]').dialog('close');
}
else {
CombodoModal.OpenInformativeModal(data.data.error_message, 'error');
}
});
}
JS
;
$aExtraParams = [
'noRelations' => true,
'fieldsFlags' => $aFieldFlags,
'js_handlers' => [
'form_on_submit' => $sFormOnSubmitJsCode,
'cancel_button_on_click' =>
<<<JS
function() {
$(this).closest('[data-role="ibo-modal"]').dialog('close');
};
JS
]
];
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aExtraParams);
}
else
{
// - We'll let the user select a class if multiple classes are available
$oClassForm = FormUIBlockFactory::MakeStandard();
// - When the user submit, redo the same request but with a real class
$sCurrentParameters = json_encode([
'att_code' => $sAttCode,
'host_class' => $sClass,
'host_id' => $sId]);
$sCurrentUrl = utils::GetAbsoluteUrlAppRoot().'/pages/UI.php?route=linkset.create_linked_object';
$oClassForm->SetOnSubmitJsCode(
<<<JS
let me = this;
let aParam = $sCurrentParameters;
aParam['class'] = $(this).find('[name="class"]').val();
let sPosting = $.post('$sCurrentUrl', aParam);
sPosting.done(function(data){
$(me).closest('[data-role="ibo-modal"]').html(data);
});
return false;
JS
);
// - Add a select and a button to validate the form
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sProposedRealClass, MetaModel::GetName($sProposedRealClass), $aPossibleClasses));
$oPage->AddUiBlock($oClassForm);
}
return $oPage;
}
}