Files
iTop/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php

1532 lines
61 KiB
PHP

<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// 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/>
namespace Combodo\iTop\Portal\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Exception;
use FileUploadException;
use utils;
use Dict;
use IssueLog;
use MetaModel;
use DBObject;
use DBSearch;
use DBObjectSearch;
use FalseExpression;
use BinaryExpression;
use FieldExpression;
use VariableExpression;
use ListExpression;
use ScalarExpression;
use DBObjectSet;
use AttributeEnum;
use AttributeImage;
use AttributeFinalClass;
use AttributeFriendlyName;
use UserRights;
use iPopupMenuExtension;
use URLButtonItem;
use JSButtonItem;
use Combodo\iTop\Portal\Helper\ApplicationHelper;
use Combodo\iTop\Portal\Helper\SecurityHelper;
use Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
use Combodo\iTop\Portal\Form\ObjectFormManager;
use Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
/**
* Class ObjectController
*
* Controller to handle basic view / edit / create of cmdbAbstractObjectClass ManageBrickController
*
* @package Combodo\iTop\Portal\Controller
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @since 2.3.0
*/
class ObjectController extends AbstractController
{
const ENUM_MODE_VIEW = 'view';
const ENUM_MODE_EDIT = 'edit';
const ENUM_MODE_CREATE = 'create';
const DEFAULT_PAGE_NUMBER = 1;
const DEFAULT_LIST_LENGTH = 10;
/**
* Displays an cmdbAbstractObject if the connected user is allowed to.
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sObjectClass (Class must be instance of cmdbAbstractObject)
* @param string $sObjectId
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function ViewAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
{
// Checking parameters
if ($sObjectClass === '' || $sObjectId === '')
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
$oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
}
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass, $sObjectId))
{
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . '::' . $sObjectId . ' object.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$aData = array('sMode' => 'view');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass), $oObject->GetName());
// Add an edit button if user is allowed
if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId))
{
$oModifyButton = new URLButtonItem(
'modify_object',
Dict::S('UI:Menu:Modify'),
$oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
);
// Putting this one first
$aData['form']['buttons']['links'][] = $oModifyButton->GetMenuItem();
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
else
{
$oResponse = $oApp->json($aData);
}
}
else
{
// Adding brick if it was passed
$sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
if (!empty($sBrickId))
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if ($oBrick !== null)
{
$aData['oBrick'] = $oBrick;
}
}
$aData['sPageTitle'] = $aData['form']['title'];
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
}
return $oResponse;
}
/**
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param $sObjectClass
* @param $sObjectId
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function EditAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
{
// Checking parameters
if ($sObjectClass === '' || $sObjectId === '')
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
$oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
}
// Checking security layers
// Warning : This is a dirty quick fix to allow editing its own contact information
$bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite)
{
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$aData = array('sMode' => 'edit');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Edit:Title', MetaModel::GetName($sObjectClass), $aData['form']['object_name']);
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
else
{
$oResponse = $oApp->json($aData);
}
}
else
{
// Adding brick if it was passed
$sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
if (!empty($sBrickId))
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if ($oBrick !== null)
{
$aData['oBrick'] = $oBrick;
}
}
$aData['sPageTitle'] = $aData['form']['title'];
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
}
return $oResponse;
}
/**
* Creates an cmdbAbstractObject of the $sObjectClass
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sObjectClass
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function CreateAction(Request $oRequest, Application $oApp, $sObjectClass)
{
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_CREATE, $sObjectClass))
{
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to create ' . $sObjectClass . ' object.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$aData = array('sMode' => 'create');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Create:Title', MetaModel::GetName($sObjectClass));
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
else
{
$oResponse = $oApp->json($aData);
}
}
else
{
// Adding brick if it was passed
$sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
if (!empty($sBrickId))
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if ($oBrick !== null)
{
$aData['oBrick'] = $oBrick;
}
}
$aData['sPageTitle'] = $aData['form']['title'];
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
}
return $oResponse;
}
/**
* Creates an cmdbAbstractObject of a class determined by the method encoded in $sEncodedMethodName.
* This method use an origin DBObject in order to determine the created cmdbAbstractObject.
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sObjectClass Class of the origin object
* @param string $sObjectId ID of the origin object
* @param string $sEncodedMethodName Base64 encoded factory method name
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function CreateFromFactoryAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sEncodedMethodName)
{
$sMethodName = base64_decode($sEncodedMethodName);
// Checking that the factory method is valid
if (!is_callable($sMethodName))
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Invalid factory method "' . $sMethodName . '" used when creating an object.');
$oApp->abort(500, 'Invalid factory method "' . $sMethodName . '" used when creating an object');
}
// Retrieving origin object
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
// Retrieving target object (We check if the method is a simple function or if it's part of a class in which case only static function are supported)
if (!strpos($sMethodName, '::'))
{
$oTargetObject = $sMethodName($oOriginObject);
}
else
{
$aMethodNameParts = explode('::', $sMethodName);
$sMethodClass = $aMethodNameParts[0];
$sMethodName = $aMethodNameParts[1];
$oTargetObject = $sMethodClass::$sMethodName($oOriginObject);
}
// Preparing redirection
// - Route
$aRouteParams = array(
'sObjectClass' => get_class($oTargetObject)
);
$sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
// - Request
$oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
return $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
}
/**
* Applies a stimulus $sStimulus on an cmdbAbstractObject
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sObjectClass
* @param string $sObjectId
* @param string $sStimulusCode
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function ApplyStimulusAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sStimulusCode)
{
// Checking parameters
if ($sObjectClass === '' || $sObjectId === '' || $sStimulusCode === '')
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, sObjectId and $sStimulusCode expected, "' . $sObjectClass . '", "' . $sObjectId . '" and "' . $sStimulusCode . '" given.');
$oApp->abort(500, Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus'));
}
// Checking security layers
if(!SecurityHelper::IsStimulusAllowed($oApp, $sStimulusCode, $sObjectClass))
{
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving request parameters
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
// Retrieving form properties
$aStimuliForms = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, 'apply_stimulus');
if(array_key_exists($sStimulusCode, $aStimuliForms))
{
$aFormProperties = $aStimuliForms[$sStimulusCode];
}
// Or preparing a default form for the stimulus application
else
{
// Preparing default form
$aFormProperties = array(
'id' => 'apply-stimulus',
'type' => 'custom_list',
'fields' => array(),
'layout' => null
);
}
// Adding stimulus code to form
$aFormProperties['stimulus_code'] = $sStimulusCode;
// Adding target_state to current_values
$oRequest->request->set('apply_stimulus', array('code' => $sStimulusCode));
$aData = array('sMode' => 'apply_stimulus');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Stimulus:Title');
$aData['form']['validation']['redirection'] = array(
'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
);
// TODO : This is a ugly patch to avoid showing a modal with a readonly form to the user as it would prevent user from finishing the transition.
// Instead, we apply the stimulus directly here and then go to the edited object.
if (empty($sOperation))
{
if (isset($aData['form']['editable_fields_count']) && $aData['form']['editable_fields_count'] === 0)
{
$sOperation = 'redirect';
$oSubRequest = $oRequest;
$oSubRequest->request->set('operation', 'submit');
$oSubRequest->request->set('stimulus_code', '');
$aData = array('sMode' => 'apply_stimulus');
$aData['form'] = $this->HandleForm($oSubRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
// Redefining the array to be as simple as possible :
$aData = array('redirection' =>
array('url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)))
);
}
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
elseif ($sOperation === 'redirect')
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/modal/mode_loader.html.twig', $aData);
}
else
{
$oResponse = $oApp->json($aData);
}
}
else
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
}
return $oResponse;
}
/**
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sMode
* @param string $sObjectClass
* @param string $sObjectId
* @param string $aFormProperties
*
* @return array
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
* @throws \Twig_Error_Loader
* @throws \Twig_Error_Runtime
* @throws \Twig_Error_Syntax
*/
public static function HandleForm(Request $oRequest, Application $oApp, $sMode, $sObjectClass, $sObjectId = null, $aFormProperties = null)
{
$aFormData = array();
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation));
// - Retrieve form properties
if ($aFormProperties === null)
{
$aFormProperties = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, $sMode);
}
// - Create and
if (empty($sOperation))
{
// Retrieving action rules
//
// Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
// But it would not be a security issue as it only presets values in the form.
$sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
$aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
// Preparing object
if ($sObjectId === null)
{
// Create new UserRequest
$oObject = MetaModel::NewObject($sObjectClass);
// Retrieve action rules information to auto-fill the form if available
// Preparing object
$oApp['context_manipulator']->PrepareObject($aActionRules, $oObject);
$aPrefillFormParam = array( 'user' => $_SESSION["auth_user"],
'origin' => 'portal');
$oObject->PrefillForm('creation_from_0', $aPrefillFormParam);
}
else
{
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
}
// Preparing buttons
$aFormData['buttons'] = array(
'transitions' => array(),
'actions' => array(),
'links' => array(),
'submit' => array(
'label' => Dict::S('Portal:Button:Submit'),
),
);
if ($sMode !== 'apply_stimulus')
{
// Add transition buttons
$oSetToCheckRights = DBObjectSet::FromObject($oObject);
$aStimuli = Metamodel::EnumStimuli($sObjectClass);
foreach ($oObject->EnumTransitions() as $sStimulusCode => $aTransitionDef)
{
if(SecurityHelper::IsStimulusAllowed($oApp, $sStimulusCode, $sObjectClass, $oSetToCheckRights))
{
$aFormData['buttons']['transitions'][$sStimulusCode] = $aStimuli[$sStimulusCode]->GetLabel();
}
}
// Add plugin buttons
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
{
foreach($oExtensionInstance->EnumItems(iPopupMenuExtension::PORTAL_OBJDETAILS_ACTIONS, array('portal_id' => $oApp['combodo.portal.instance.id'], 'object' => $oObject)) as $oMenuItem)
{
if (is_object($oMenuItem))
{
if($oMenuItem instanceof JSButtonItem)
{
$aFormData['buttons']['actions'][] = $oMenuItem->GetMenuItem() + array('js_files' => $oMenuItem->GetLinkedScripts());
}
elseif($oMenuItem instanceof URLButtonItem)
{
$aFormData['buttons']['links'][] = $oMenuItem->GetMenuItem();
}
}
}
}
// Hiding submit button or changing its label if necessary
if(!empty($aFormData['buttons']['transitions']) && isset($aFormProperties['properties']) &&$aFormProperties['properties']['always_show_submit'] === false)
{
unset($aFormData['buttons']['submit']);
}
elseif($sMode === static::ENUM_MODE_EDIT)
{
$aFormData['buttons']['submit']['label'] = Dict::S('Portal:Button:Apply');
}
}
else
{
$aPrefillFormParam = array(
'user' => $_SESSION["auth_user"],
'origin' => 'portal',
'stimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)['code'],
);
$oObject->PrefillForm('state_change', $aPrefillFormParam);
}
// Preparing callback urls
$aCallbackUrls = $oApp['context_manipulator']->GetCallbackUrls($oApp, $aActionRules, $oObject, $bModal);
$aFormData['submit_callback'] = $aCallbackUrls['submit'];
$aFormData['cancel_callback'] = $aCallbackUrls['cancel'];
// Preparing renderer
// Note : We might need to distinguish form & renderer endpoints
if (in_array($sMode, array('create', 'edit', 'view')))
{
$sFormEndpoint = $oApp['url_generator']->generate('p_object_' . $sMode, array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId));
}
else
{
$sFormEndpoint = $_SERVER['REQUEST_URI'];
}
$oFormRenderer = new BsFormRenderer();
$oFormRenderer->SetEndpoint($sFormEndpoint);
$oFormManager = new ObjectFormManager();
$oFormManager->SetApplication($oApp)
->SetObject($oObject)
->SetMode($sMode)
->SetActionRulesToken($sActionRulesToken)
->SetRenderer($oFormRenderer)
->SetFormProperties($aFormProperties);
$oFormManager->Build();
// Check the number of editable fields
$aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
}
else
{
// Update / Submit / Cancel
$sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
$sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
if ( empty($sFormManagerClass) || empty($sFormManagerData) )
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
$oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
}
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
$oFormManager->SetApplication($oApp);
// Applying action rules if present
if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
{
$aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
$oObj = $oFormManager->GetObject();
$oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
$oFormManager->SetObject($oObj);
}
switch ($sOperation)
{
case 'submit':
// Applying modification to object
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'attachmentIds' => $oApp['request_manipulator']->ReadParam('attachment_ids', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties, 'applyStimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)));
if ($aFormData['validation']['valid'] === true)
{
// Note : We don't use $sObjectId there as it can be null if we are creating a new one. Instead we use the id from the created object once it has been seralized
// Check if stimulus has to be applied
$sStimulusCode = $oApp['request_manipulator']->ReadParam('stimulus_code', '');
if (!empty($sStimulusCode))
{
$aFormData['validation']['redirection'] = array(
'url' => $oApp['url_generator']->generate('p_object_apply_stimulus', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey(), 'sStimulusCode' => $sStimulusCode)),
'ajax' => true
);
}
// Otherwise, we show the object if there is no default
// else
// {
// $aFormData['validation']['redirection'] = array(
// 'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
// );
// }
}
break;
case 'update':
$oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties));
break;
case 'cancel':
$oFormManager->OnCancel();
break;
}
}
// Preparing field_set data
$aFieldSetData = array(
//'fields_list' => $oFormManager->GetRenderer()->Render(), // GLA : This should be done just after in the if statement.
'fields_impacts' => $oFormManager->GetForm()->GetFieldsImpacts(),
'form_path' => $oFormManager->GetForm()->GetId()
);
// Preparing fields list regarding the operation
if ($sOperation === 'update')
{
$aRequestedFields = $oApp['request_manipulator']->ReadParam('requested_fields', array(), FILTER_UNSAFE_RAW);
$sFormPath = $oApp['request_manipulator']->ReadParam('form_path', '');
// Checking if the update was on a subform, if so we need to make the rendering for that part only
if ( !empty($sFormPath) && $sFormPath !== $oFormManager->GetForm()->GetId() )
{
$oSubForm = $oFormManager->GetForm()->FindSubForm($sFormPath);
$oSubFormRenderer = new BsFormRenderer($oSubForm);
$oSubFormRenderer->SetEndpoint($oFormManager->GetRenderer()->GetEndpoint());
$aFormData['updated_fields'] = $oSubFormRenderer->Render($aRequestedFields);
}
else
{
$aFormData['updated_fields'] = $oFormManager->GetRenderer()->Render($aRequestedFields);
}
}
else
{
$aFieldSetData['fields_list'] = $oFormManager->GetRenderer()->Render();
}
// Preparing form data
$aFormData['id'] = $oFormManager->GetForm()->GetId();
$aFormData['transaction_id'] = $oFormManager->GetForm()->GetTransactionId();
$aFormData['formmanager_class'] = $oFormManager->GetClass();
$aFormData['formmanager_data'] = $oFormManager->ToJSON();
$aFormData['renderer'] = $oFormManager->GetRenderer();
$aFormData['object_name'] = $oFormManager->GetObject()->GetName();
$aFormData['object_state'] = $oFormManager->GetObject()->GetState();
$aFormData['fieldset'] = $aFieldSetData;
$aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
return $aFormData;
}
/**
* Handles the autocomplete search
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
* @param string $sHostObjectClass Class name of the host object
* @param string $sHostObjectId Id of the host object
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*/
public function SearchAutocompleteAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
{
$aData = array(
'results' => array(
'count' => 0,
'items' => array()
)
);
// Parsing parameters from request payload
parse_str($oRequest->getContent(), $aRequestContent);
// Checking parameters
if (!isset($aRequestContent['sQuery']))
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameter sQuery missing.');
$oApp->abort(500, Dict::Format('UI:Error:ParameterMissing', 'sQuery'));
}
// Retrieving parameters
$sQuery = $aRequestContent['sQuery'];
$sFieldId = $aRequestContent['sFieldId'];
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
{
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sHostObjectClass . '::' . $sHostObjectId . '.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving host object for future DBSearch parameters
if ($sHostObjectId !== null)
{
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
}
else
{
$oHostObject = MetaModel::NewObject($sHostObjectClass);
// Retrieving action rules
//
// Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
// But it would not be a security issue as it only presets values in the form.
$sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
$aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
// Preparing object
$oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
}
// Updating host object with form data / values
$sFormManagerClass = $aRequestContent['formmanager_class'];
$sFormManagerData = $aRequestContent['formmanager_data'];
if (!empty($sFormManagerClass) && !empty($sFormManagerData))
{
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
$oFormManager->SetApplication($oApp);
$oFormManager->SetObject($oHostObject);
// Applying action rules if present
if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
{
$aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
$oObj = $oFormManager->GetObject();
$oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
$oFormManager->SetObject($oObj);
}
// Updating host object
$oFormManager->OnUpdate(array('currentValues' => $aRequestContent['current_values']));
$oHostObject = $oFormManager->GetObject();
}
// Building search query
// - Retrieving target object class from attcode
$oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oRequestTemplate = $oHostObject->Get($sTargetAttCode);
$oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
$sTargetObjectClass = $oTemplateFieldSearch->GetClass();
}
elseif ($oTargetAttDef->IsLinkSet())
{
throw new Exception('Search autocomplete cannot apply on AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' (' . $sHostObjectClass . '->' . $sTargetAttCode . ') given.');
}
else
{
$sTargetObjectClass = $oTargetAttDef->GetTargetClass();
}
// - Base query from meta model
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oSearch = $oTemplateFieldSearch;
}
else
{
$oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
}
// - Adding query condition
$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
// - Intersecting with scope constraints
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
// It is the responsability of the template designer to write the right query so the user see only what he should.
if ($oTargetAttDef->GetEditClass() !== 'CustomFields')
{
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
$oSearch = $oSearch->Intersect($oScopeSearch);
// - Allowing all data if necessary
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
}
// Retrieving results
// - Preparing object set
$oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%' . $sQuery . '%'));
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
// Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oSet->SetLimit(static::DEFAULT_LIST_LENGTH);
}
else
{
$oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
}
// - Retrieving objects
while ($oItem = $oSet->Fetch())
{
$aData['results']['items'][] = array('id' => $oItem->GetKey(), 'name' => html_entity_decode($oItem->GetName(), ENT_QUOTES, 'UTF-8'));
$aData['results']['count'] ++;
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
$oResponse = $oApp->json($aData);
}
else
{
$oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
return $oResponse;
}
/**
* Handles the regular (table) search from an attribute
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
* @param string $sHostObjectClass Class name of the host object
* @param string $sHostObjectId Id of the host object
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \OQLException
*/
public function SearchFromAttributeAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
{
$aData = array(
'sMode' => 'search_regular',
'sTargetAttCode' => $sTargetAttCode,
'sHostObjectClass' => $sHostObjectClass,
'sHostObjectId' => $sHostObjectId,
'sActionRulesToken' => $oApp['request_manipulator']->ReadParam('ar_token', ''),
);
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
{
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sHostObjectClass . '::' . $sHostObjectId . ' object.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving host object for future DBSearch parameters
if ($sHostObjectId !== null)
{
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
}
else
{
$oHostObject = MetaModel::NewObject($sHostObjectClass);
// Retrieving action rules
//
// Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
// But it would not be a security issue as it only presets values in the form.
$aActionRules = !empty($aData['sActionRulesToken']) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
// Preparing object
$oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
}
// Updating host object with form data / values
$sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
$sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
if ( !empty($sFormManagerClass) && !empty($sFormManagerData) )
{
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
$oFormManager->SetApplication($oApp);
$oFormManager->SetObject($oHostObject);
// Applying action rules if present
if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
{
$aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
$oObj = $oFormManager->GetObject();
$oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
$oFormManager->SetObject($oObj);
}
// Updating host object
$oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW)));
$oHostObject = $oFormManager->GetObject();
}
// Retrieving request parameters
$iPageNumber = $oApp['request_manipulator']->ReadParam('iPageNumber', static::DEFAULT_PAGE_NUMBER, FILTER_SANITIZE_NUMBER_INT);
$iListLength = $oApp['request_manipulator']->ReadParam('iListLength', static::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT);
$bInitalPass = $oApp['request_manipulator']->HasParam('draw') ? false : true;
$sQuery = $oApp['request_manipulator']->ReadParam('sSearchValue', '');
$sFormPath = $oApp['request_manipulator']->ReadParam('sFormPath', '');
$sFieldId = $oApp['request_manipulator']->ReadParam('sFieldId', '');
$aObjectIdsToIgnore = $oApp['request_manipulator']->ReadParam('aObjectIdsToIgnore', null, FILTER_UNSAFE_RAW);
// Building search query
// - Retrieving target object class from attcode
$oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
if ($oTargetAttDef->IsExternalKey())
{
$sTargetObjectClass = $oTargetAttDef->GetTargetClass();
}
elseif ($oTargetAttDef->IsLinkSet())
{
if (!$oTargetAttDef->IsIndirect())
{
$sTargetObjectClass = $oTargetAttDef->GetLinkedClass();
}
else
{
$oRemoteAttDef = MetaModel::GetAttributeDef($oTargetAttDef->GetLinkedClass(), $oTargetAttDef->GetExtKeyToRemote());
$sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
}
}
elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oRequestTemplate = $oHostObject->Get($sTargetAttCode);
$oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
$sTargetObjectClass = $oTemplateFieldSearch->GetClass();
}
else
{
throw new Exception('Search from attribute can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
}
// - Retrieving class attribute list
$aAttCodes = ApplicationHelper::GetLoadedListFromClass($oApp, $sTargetObjectClass, 'list');
// - Adding friendlyname attribute to the list is not already in it
$sTitleAttCode = 'friendlyname';
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodes))
{
$aAttCodes = array_merge(array($sTitleAttCode), $aAttCodes);
}
// - Retrieving scope search
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
// It is the responsability of the template designer to write the right query so the user see only what he should.
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
$aInternalParams = array();
if (($oScopeSearch === null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// - Base query from meta model
if ($oTargetAttDef->IsExternalKey())
{
$oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
}
elseif ($oTargetAttDef->IsLinkSet())
{
$oSearch = $oScopeSearch;
}
elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
// Note : $oTemplateFieldSearch has been defined in the "Retrieving target object class from attcode" part, it is not available otherwise
$oSearch = $oTemplateFieldSearch;
}
// - Filtering objects to ignore
if (($aObjectIdsToIgnore !== null) && (is_array($aObjectIdsToIgnore)))
{
//$oSearch->AddConditionExpression('id', $aObjectIdsToIgnore, 'NOT IN');
$aExpressions = array();
foreach ($aObjectIdsToIgnore as $sObjectIdToIgnore)
{
$aExpressions[] = new ScalarExpression($sObjectIdToIgnore);
}
$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), 'NOT IN', new ListExpression($aExpressions)));
}
// - Adding query condition
$aInternalParams['this'] = $oHostObject;
if (!empty($sQuery))
{
$oFullExpr = null;
for ($i = 0; $i < count($aAttCodes); $i++)
{
// Checking if the current attcode is an external key in order to search on the friendlyname
$oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
$sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
// Building expression for the current attcode
// - For attributes that need conversion from their display value to storage value
// Note : This is dirty hack that will need to be refactored in the OQL core in order to be nicer and to be extended to other types such as dates etc...
if (($oAttDef instanceof AttributeEnum) || ($oAttDef instanceof AttributeFinalClass))
{
// Looking up storage value
$aMatchedCodes = array();
foreach ($oAttDef->GetAllowedValues() as $sValueCode => $sValueLabel)
{
if (stripos($sValueLabel, $sQuery) !== false)
{
$aMatchedCodes[] = $sValueCode;
}
}
// Building expression
if (!empty($aMatchedCodes))
{
$oEnumeratedListExpr = ListExpression::FromScalars($aMatchedCodes);
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'IN', $oEnumeratedListExpr);
}
else
{
$oBinExpr = new FalseExpression();
}
}
// - For regular attributs
else
{
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
}
// Adding expression to the full expression (all attcodes)
if ($i === 0)
{
$oFullExpr = $oBinExpr;
}
else
{
$oFullExpr = new BinaryExpression($oFullExpr, 'OR', $oBinExpr);
}
}
// Adding full expression to the search object
$oSearch->AddConditionExpression($oFullExpr);
$aInternalParams['re_query'] = '%' . $sQuery . '%';
}
// - Intersecting with scope constraints
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
// It is the responsability of the template designer to write the right query so the user see only what he should.
if (($oScopeSearch !== null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
{
$oSearch = $oSearch->Intersect($oScopeSearch);
// - Allowing all data if necessary
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
}
// Retrieving results
// - Preparing object set
$oSet = new DBObjectSet($oSearch, array(), $aInternalParams);
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aAttCodes));
$oSet->SetLimit($iListLength, $iListLength * ($iPageNumber - 1));
// - Retrieving columns properties
$aColumnProperties = array();
foreach ($aAttCodes as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
$aColumnProperties[$sAttCode] = array(
'title' => $oAttDef->GetLabel()
);
}
// - Retrieving objects
$aItems = array();
while ($oItem = $oSet->Fetch())
{
$aItems[] = $this->PrepareObjectInformations($oApp, $oItem, $aAttCodes);
}
// Preparing response
if ($bInitalPass)
{
$aData = $aData + array(
'form' => array(
'id' => 'object_search_form_' . time(),
'title' => Dict::Format('Brick:Portal:Object:Search:Regular:Title', $oTargetAttDef->GetLabel(), MetaModel::GetName($sTargetObjectClass))
),
'aColumnProperties' => json_encode($aColumnProperties),
'aResults' => array(
'aItems' => json_encode($aItems),
'iCount' => count($aItems)
),
'bMultipleSelect' => $oTargetAttDef->IsLinkSet(),
'aSource' => array(
'sFormPath' => $sFormPath,
'sFieldId' => $sFieldId,
'aObjectIdsToIgnore' => $aObjectIdsToIgnore,
'sFormManagerClass' => $sFormManagerClass,
'sFormManagerData' => $sFormManagerData
)
);
if ($oRequest->isXmlHttpRequest())
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
else
{
//$oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
}
}
else
{
$aData = $aData + array(
'levelsProperties' => $aColumnProperties,
'data' => $aItems,
'recordsTotal' => $oSet->Count(),
'recordsFiltered' => $oSet->Count()
);
$oResponse = $oApp->json($aData);
}
return $oResponse;
}
/**
* Handles the hierarchical search from an attribute
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
* @param string $sHostObjectClass Class name of the host object
* @param string $sHostObjectId Id of the host object
*
* @return void
*
*/
public function SearchHierarchyAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
{
// TODO
}
/**
* Handles ormDocument display / download from an object
*
* Note: This is inspired from pages/ajax.document.php, but duplicated as there is no secret mecanism for ormDocument yet.
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sOperation
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function DocumentAction(Request $oRequest, Application $oApp, $sOperation = null)
{
// Setting default operation
if($sOperation === null)
{
$sOperation = 'display';
}
// Retrieving ormDocument's host object
$sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
$sObjectId = $oApp['request_manipulator']->ReadParam('sObjectId', '');
$sObjectField = $oApp['request_manipulator']->ReadParam('sObjectField', '');
// When reaching to an Attachment, we have to check security on its host object instead of the Attachment itself
if($sObjectClass === 'Attachment')
{
$oAttachment = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
$sHostClass = $oAttachment->Get('item_class');
$sHostId = $oAttachment->Get('item_id');
}
else
{
$sHostClass = $sObjectClass;
$sHostId = $sObjectId;
}
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostClass, $sHostId))
{
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to retrieve document from attribute ' . $sObjectField . ' as it not allowed to read ' . $sHostClass . '::' . $sHostId . ' object.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* Must not be found */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sHostClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Setting cache timeout
// Note: Attachment download should be handle through AttachmentAction()
if($sObjectClass === 'Attachment')
{
// One year ahead: an attachement cannot change
$iCacheSec = 31556926;
}
else
{
$iCacheSec = $oApp['request_manipulator']->ReadParam('cache', 0, FILTER_SANITIZE_NUMBER_INT);
}
$aHeaders = array();
if($iCacheSec > 0)
{
$aHeaders['Expires'] = '';
$aHeaders['Cache-Control'] = 'no-transform, public,max-age='.$iCacheSec.',s-maxage='.$iCacheSec;
// Reset the value set previously
$aHeaders['Pragma'] = 'cache';
// An arbitrary date in the past is ok
$aHeaders['Last-Modified'] = 'Wed, 15 Jun 2015 13:21:15 GMT';
}
/** @var \ormDocument $oDocument */
$oDocument = $oObject->Get($sObjectField);
$aHeaders['Content-Type'] = $oDocument->GetMimeType();
$aHeaders['Content-Disposition'] = (($sOperation === 'display') ? 'inline' : 'attachment') . ';filename="'.$oDocument->GetFileName().'"';
return new Response($oDocument->GetData(), Response::HTTP_OK, $aHeaders);
}
/**
* Handles attachment add/remove on an object
*
* Note: This is inspired from itop-attachment/ajax.attachment.php
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sOperation
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
* @throws \CoreException
* @throws \CoreUnexpectedValue
*/
public function AttachmentAction(Request $oRequest, Application $oApp, $sOperation = null)
{
$aData = array(
'att_id' => 0,
'preview' => false,
'msg' => ''
);
// Retrieving sOperation from request only if it wasn't forced (determined by the route)
if ($sOperation === null)
{
$sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
}
switch ($sOperation)
{
case 'add':
$sFieldName = $oApp['request_manipulator']->ReadParam('field_name', '');
$sObjectClass = $oApp['request_manipulator']->ReadParam('object_class', '');
$sTempId = $oApp['request_manipulator']->ReadParam('temp_id', '');
if (empty($sObjectClass) || empty($sTempId))
{
$aData['error'] = Dict::Format('UI:Error:2ParametersMissing', 'object_class', 'temp_id');
}
else
{
try
{
$oDocument = utils::ReadPostedDocument($sFieldName);
$oAttachment = MetaModel::NewObject('Attachment');
$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime')); // one hour...
$oAttachment->Set('temp_id', $sTempId);
$oAttachment->Set('item_class', $sObjectClass);
$oAttachment->SetDefaultOrgId();
$oAttachment->Set('contents', $oDocument);
$iAttId = $oAttachment->DBInsert();
$aData['msg'] = htmlentities($oDocument->GetFileName(), ENT_QUOTES, 'UTF-8');
// TODO : Change icon location when itop-attachment is refactored
//$aData['icon'] = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($oDoc->GetFileName());
$aData['icon'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-attachments/icons/image.png';
$aData['att_id'] = $iAttId;
$aData['preview'] = $oDocument->IsPreviewAvailable() ? 'true' : 'false';
}
catch (FileUploadException $e)
{
$aData['error'] = $e->GetMessage();
}
}
// Note : The Content-Type header is set to 'text/plain' in order to be IE9 compatible. Otherwise ('application/json') IE9 will download the response as a JSON file to the user computer...
$oResponse = $oApp->json($aData, 200, array('Content-Type' => 'text/plain'));
break;
case 'download':
// Preparing redirection
// - Route
$aRouteParams = array(
'sObjectClass' => 'Attachment',
'sObjectId' => $oApp['request_manipulator']->ReadParam('sAttachmentId', null),
'sObjectField' => 'contents',
);
$sRedirectRoute = $oApp['url_generator']->generate('p_object_document_download', $aRouteParams);
// - Request
$oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
$oResponse = $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
break;
default:
$oApp->abort(403);
break;
}
return $oResponse;
}
/**
* Returns a json response containing an array of objects informations.
*
* The service must be given 3 parameters :
* - sObjectClass : The class of objects to retrieve information from
* - aObjectIds : An array of object ids
* - aObjectAttCodes : An array of attribute codes to retrieve
*
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \OQLException
* @throws \CoreException
*/
public function GetInformationsAsJsonAction(Request $oRequest, Application $oApp)
{
$aData = array();
// Retrieving parameters
$sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
$aObjectIds = $oApp['request_manipulator']->ReadParam('aObjectIds', array(), FILTER_UNSAFE_RAW);
$aObjectAttCodes = $oApp['request_manipulator']->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
if ( empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes) )
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, aObjectIds and aObjectAttCodes expected, "' . $sObjectClass . '", "' . implode('/', $aObjectIds) . '" given.');
$oApp->abort(500, 'Invalid request data, some informations are missing');
}
// Checking that id is in the AttCodes
if (!in_array('id', $aObjectAttCodes))
{
$aObjectAttCodes = array_merge(array('id'), $aObjectAttCodes);
}
// Building the search
$bIgnoreSilos = $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
$oSearch = DBObjectSearch::FromOQL("SELECT " . $sObjectClass . " WHERE id IN ('" . implode("','", $aObjectIds) . "')");
if ($bIgnoreSilos === true)
{
$oSearch->AllowAllData();
}
$oSet = new DBObjectSet($oSearch);
$oSet->OptimizeColumnLoad($aObjectAttCodes);
// Retrieving objects
while ($oObject = $oSet->Fetch())
{
$aData['items'][] = $this->PrepareObjectInformations($oApp, $oObject, $aObjectAttCodes);
}
return $oApp->json($aData);
}
/**
* Prepare a DBObject informations as an array for a client side usage (typically, add a row in a table)
*
* @param \Silex\Application $oApp
* @param \DBObject $oObject
* @param array $aAttCodes
*
* @return array
*
* @throws \Exception
* @throws \CoreException
*/
protected function PrepareObjectInformations(Application $oApp, DBObject $oObject, $aAttCodes = array())
{
$sObjectClass = get_class($oObject);
$aObjectData = array(
'id' => $oObject->GetKey(),
'name' => $oObject->GetName(),
'attributes' => array(),
);
// Retrieving attributes definitions
$aAttDefs = array();
foreach ($aAttCodes as $sAttCode)
{
if ($sAttCode === 'id')
continue;
$aAttDefs[$sAttCode] = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
}
// Preparing attribute data
foreach ($aAttDefs as $oAttDef)
{
$aAttData = array(
'att_code' => $oAttDef->GetCode()
);
if ($oAttDef->IsExternalKey())
{
$aAttData['value'] = $oObject->GetAsHTML($oAttDef->GetCode() . '_friendlyname');
// Checking if user can access object's external key
if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass()))
{
$aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oObject->Get($oAttDef->GetCode())));
}
}
elseif ($oAttDef->IsLinkSet())
{
// We skip it
continue;
}
elseif ($oAttDef instanceof AttributeImage)
{
$oOrmDoc = $oObject->Get($oAttDef->GetCode());
if (is_object($oOrmDoc) && !$oOrmDoc->IsEmpty())
{
$sUrl = $oApp['url_generator']->generate('p_object_document_display', array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey(), 'sObjectField' => $oAttDef->GetCode(), 'cache' => 86400));
}
else
{
$sUrl = $oAttDef->Get('default_image');
}
$aAttData['value'] = '<img src="' . $sUrl . '" />';
}
else
{
$aAttData['value'] = $oAttDef->GetAsHTML($oObject->Get($oAttDef->GetCode()));
if ($oAttDef instanceof AttributeFriendlyName)
{
// Checking if user can access object
if(SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass))
{
$aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oObject->GetKey()));
}
}
}
$aObjectData['attributes'][$oAttDef->GetCode()] = $aAttData;
}
return $aObjectData;
}
}