N°1192 Introduce navigation_rules in the end-users portal

This commit is contained in:
Molkobain
2019-11-22 17:54:19 +01:00
parent ff884533f9
commit b1b6c9f426
10 changed files with 888 additions and 119 deletions

View File

@@ -121,6 +121,9 @@ services:
context_manipulator:
alias: Combodo\iTop\Portal\Helper\ContextManipulatorHelper
public: true
navigation_rule_helper:
alias: Combodo\iTop\Portal\Helper\NavigationRuleHelper
public: true
lifecycle_validator:
alias: Combodo\iTop\Portal\Helper\LifecycleValidatorHelper
public: true

View File

@@ -26,21 +26,35 @@ $(function()
$.widget( 'itop.portal_form_handler', $.itop.form_handler,
{
options: {
submit_url: null,
cancel_url: null
submit_url: null, // Deprecated. We kept those properties to preserve compatibility with extensions
cancel_url: null, // but you should start using xxx_rule.url as soon as possible.
submit_rule: {
category: 'redirect',
url: null,
modal: false,
},
cancel_rule: {
category: 'close',
url: null,
modal: false,
},
},
// the constructor
_create: function()
{
this.element
.addClass('portal_form_handler');
this.element.addClass('portal_form_handler');
// Safe check for options
if(this.options.submit_url === "")
this.options.submit_url = null;
if(this.options.cancel_url === "")
this.options.cancel_url = null;
if(this.options.submit_rule.url === '')
this.options.submit_rule.url = null;
if(this.options.cancel_rule.url === '')
this.options.cancel_rule.url = null;
// Deprecated, see this.options.submit_url
if((this.options.submit_url !== null) && (this.options.submit_url !== ''))
this.options.submit_rule.url = this.options.submit_url;
if((this.options.cancel_url !== null) && (this.options.cancel_url !== ''))
this.options.cancel_rule.url = this.options.cancel_url;
this._super();
},
@@ -160,69 +174,34 @@ $(function()
// If everything is okay, we close the form and reload it.
if(oValidation.valid)
{
var bRedirectInModal = me.options.submit_rule.modal;
var sRedirectUrl = me.options.submit_rule.url;
$('body').trigger('unregister_blocker.portal.itop', {'sBlockerId': me.element.attr('id')});
if(me.options.is_modal)
{
me.element.closest('.modal').modal('hide');
}
// Checking if we have to redirect to another page
// Typically this happens when applying a stimulus, we redirect to transition form
if(oValidation.redirection !== undefined)
{
var oRedirection = oValidation.redirection;
var bRedirectionAjax = (oRedirection.ajax !== undefined) ? oRedirection.ajax : false;
var sUrl = null;
// URL priority order :
// redirection.url > me.option.submit_url > redirection.alternative_url
if(oRedirection.modal !== undefined)
{
bRedirectInModal = oRedirection.modal;
}
if(oRedirection.url !== undefined)
{
sUrl = oRedirection.url;
sRedirectUrl = oRedirection.url;
}
else if(me.options.submit_url !== null)
me._applyRedirectRule(sRedirectUrl, bRedirectInModal);
}
else if(me.options.submit_rule.category === 'redirect')
{
sUrl = me.options.submit_url;
me._applyRedirectRule(sRedirectUrl, bRedirectInModal);
}
else if(oRedirection.alternative_url !== undefined)
// Close rule only needs to be applied to non modal forms (modal is always closed on submit)
else if(me.options.submit_rule.category === 'close')
{
sUrl = oRedirection.alternative_url;
}
if(sUrl !== null)
{
if(bRedirectionAjax)
{
// Creating a new modal
CombodoPortalToolbox.OpenModal({
content: {
endpoint: sUrl,
data: {
// Passing form manager data to the next page, just in case it needs it (eg. when applying stimulus)
formmanager_class: me.options.formmanager_class,
formmanager_data: JSON.stringify(me.options.formmanager_data)
},
},
});
}
else
{
// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
// Note : We use a timeout because .always() is called right after here and will hide the loader
setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
// Redirecting after a few ms so the user can see what happend
setTimeout(function() { location.href = sUrl; }, 400);
}
}
}
else if(me.options.submit_url !== null)
{
// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
// Note : We use a timeout because .always() is called right after here and will hide the loader
setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
// Redirecting after a few ms so the user can see what happend
setTimeout(function() { location.href = me.options.submit_url; }, 400);
me._applyCloseRule();
}
}
}
@@ -269,38 +248,31 @@ $(function()
},
function(oData)
{
if(me.options.cancel_url !== null)
if(me.options.cancel_rule.category === 'redirect')
{
location.href = me.options.cancel_url;
me._applyRedirectRule(me.options.cancel_rule.url, me.options.cancel_rule.modal);
}
else if(me.options.cancel_rule.category === 'close')
{
me._applyCloseRule();
}
}
)
.always(function(){
// Close the modal only if fields had to be cancelled
if(me.options.is_modal)
.always(function()
{
me.element.closest('.modal').modal('hide');
}
me._enableFormAfterLoading();
});
}
// Otherwise we can close the modal immediately
else
{
if(me.options.cancel_url !== null)
if(me.options.cancel_rule.category === 'redirect')
{
location.href = me.options.cancel_url;
me._applyRedirectRule(me.options.cancel_rule.url, me.options.cancel_rule.modal);
}
else
else if(me.options.cancel_rule.category === 'close')
{
if(me.options.is_modal)
{
me.element.closest('.modal').modal('hide');
}
else
{
location.reload();
}
me._applyCloseRule();
}
}
},
@@ -343,6 +315,53 @@ $(function()
{
$('#page_overlay').fadeOut(200);
},
_applyRedirectRule: function(sRedirectUrl, bRedirectInModal)
{
var me = this;
// Always close current modal
if(this.options.is_modal)
{
this.element.closest('.modal').modal('hide');
}
if(sRedirectUrl !== null)
{
if(bRedirectInModal === true)
{
// Creating a new modal
CombodoPortalToolbox.OpenModal({
content: {
endpoint: sRedirectUrl,
data: {
// Passing form manager data to the next page, just in case it needs it (eg. when applying stimulus)
formmanager_class: this.options.formmanager_class,
formmanager_data: JSON.stringify(this.options.formmanager_data)
},
},
});
}
else
{
// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
// Note: We use a timeout because .always() is called right after here and will hide the loader
setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
// Redirecting after a few ms so the user can see what happend
setTimeout(function() { location.href = sRedirectUrl; }, 400);
}
}
},
_applyCloseRule: function()
{
if(this.options.is_modal)
{
this.element.closest('.modal').modal('hide');
}
else
{
window.close();
}
},
submit: function(oEvent)
{
this._onSubmitClick(oEvent);

View File

@@ -15,12 +15,11 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*
*
*/
namespace Combodo\iTop\Portal\DependencyInjection\SilexCompatBootstrap\PortalXmlConfiguration;
use Combodo\iTop\Portal\Helper\NavigationRuleHelper;
use Symfony\Component\DependencyInjection\Container;
use DOMFormatException;
use Exception;
@@ -66,7 +65,19 @@ class Forms extends AbstractConfiguration
$aFormProperties = array(
'display_mode' => ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE,
'always_show_submit' => ApplicationHelper::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT,
'navigation_rules' => array(
'submit' => array(
NavigationRuleHelper::ENUM_ORIGIN_PAGE => null,
NavigationRuleHelper::ENUM_ORIGIN_MODAL => null,
),
'cancel' => array(
NavigationRuleHelper::ENUM_ORIGIN_PAGE => null,
NavigationRuleHelper::ENUM_ORIGIN_MODAL => null,
),
),
);
$aAllowedNavRulesButtonCodes = array_keys($aFormProperties['navigation_rules']);
if ($oFormNode->GetOptionalElement('properties') !== null)
{
/** @var \MFElement $oPropertyNode */
@@ -77,9 +88,48 @@ class Forms extends AbstractConfiguration
case 'display_mode':
$aFormProperties['display_mode'] = $oPropertyNode->GetText(ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE);
break;
case 'always_show_submit':
$aFormProperties['always_show_submit'] = ($oPropertyNode->GetText('false') === 'true') ? true : false;
break;
case 'navigation_rules':
/** @var \MFElement $oNavRuleButtonNode */
foreach($oPropertyNode->childNodes as $oNavRuleButtonNode)
{
$sNavRuleButtonCode = $oNavRuleButtonNode->nodeName;
if(!in_array($sNavRuleButtonCode, $aAllowedNavRulesButtonCodes))
{
throw new DOMFormatException('navigation_rules tag must only contain '.implode('|', $aAllowedNavRulesButtonCodes).' tags, "'.$sNavRuleButtonCode.'" given.', null, null, $oPropertyNode);
}
/** @var \MFElement $oNavRuleOriginNode */
foreach($oNavRuleButtonNode->childNodes as $oNavRuleOriginNode)
{
$sNavRuleOrigin = $oNavRuleOriginNode->nodeName;
if(!in_array($sNavRuleOrigin, NavigationRuleHelper::GetAllowedOrigins()))
{
throw new DOMFormatException($sNavRuleButtonCode. ' tag must only contain '.implode('|', NavigationRuleHelper::GetAllowedOrigins()).' tags, "'.$sNavRuleOrigin.'" given.', null, null, $oPropertyNode);
}
$sNavRuleId = $oNavRuleOriginNode->GetText();
// Note: We don't check is rule exists as it would introduce a dependency to the NavigationRuleHelper service.
// Maybe we will consider it later.
if(empty($sNavRuleId))
{
throw new DOMFormatException($sNavRuleButtonCode.' tag cannot be empty.', null, null, $oPropertyNode);
}
$aFormProperties['navigation_rules'][$sNavRuleButtonCode][$sNavRuleOrigin] = $sNavRuleId;
}
// Set modal rule as the same as default is not present.
// We preset it so we don't have to make checks elsewhere in the code when using it.
if(empty($aFormProperties['navigation_rules'][$sNavRuleButtonCode][NavigationRuleHelper::ENUM_ORIGIN_MODAL]))
{
$aFormProperties['navigation_rules'][$sNavRuleButtonCode][NavigationRuleHelper::ENUM_ORIGIN_MODAL] = $aFormProperties['navigation_rules'][$sNavRuleButtonCode][NavigationRuleHelper::ENUM_ORIGIN_PAGE];
}
}
}
}
}
@@ -98,7 +148,7 @@ class Forms extends AbstractConfiguration
}
else
{
throw new DOMFormatException('Mode tag must have an id attribute', null, null,
throw new DOMFormatException('mode tag must have an id attribute', null, null,
$oFormNode);
}

View File

@@ -16,8 +16,6 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*
*
*/
namespace Combodo\iTop\Portal\Helper;
@@ -374,6 +372,10 @@ class ApplicationHelper
'properties' => array(
'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE,
'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT,
'navigation_rules' => array(
'submit' => null,
'cancel' => null,
),
),
'fields' => array(),
'layout' => array(

View File

@@ -0,0 +1,638 @@
<?php
/**
* Copyright (C) 2013-2019 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
*/
namespace Combodo\iTop\Portal\Helper;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Portal\Brick\BrickCollection;
use Combodo\iTop\Portal\Brick\ManageBrick;
use DBObject;
use DBObjectSet;
use DBSearch;
use DOMFormatException;
use DOMNodeList;
use Exception;
use ModuleDesign;
use Symfony\Component\Routing\RouterInterface;
use utils;
/**
* Class NavigationRuleHelper ⛵
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @since 2.7.0
* @package Combodo\iTop\Portal\Helper
*/
class NavigationRuleHelper
{
// Available point of origin for the navigation
const ENUM_ORIGIN_PAGE = 'default';
const ENUM_ORIGIN_MODAL = 'modal';
// Available rule categories (of rule types)
/** @var string ENUM_RULE_CAT_CLOSE (eg. close modal/window) */
const ENUM_RULE_CAT_CLOSE = 'close';
/** @var string ENUM_RULE_CAT_REDIRECT (eg. go-to-homepage, go-to-object, go-to-brick, ...) */
const ENUM_RULE_CAT_REDIRECT = 'redirect';
// Available rule types
/** @var string ENUM_RULE_CLOSE */
const ENUM_RULE_CLOSE = 'close';
/** @var string ENUM_RULE_GO_TO_HOMEPAGE */
const ENUM_RULE_GO_TO_HOMEPAGE = 'go-to-homepage';
/** @var string ENUM_RULE_GO_TO_OBJECT */
const ENUM_RULE_GO_TO_OBJECT = 'go-to-object';
/** @var string ENUM_RULE_GO_TO_BRICK */
const ENUM_RULE_GO_TO_BRICK = 'go-to-brick';
/** @var string ENUM_RULE_GO_TO_MANAGE_BRICK */
const ENUM_RULE_GO_TO_MANAGE_BRICK = 'go-to-manage-brick';
/** @var string ENUM_RULE_GO_TO_BROWSE_BRICK */
const ENUM_RULE_GO_TO_BROWSE_BRICK = 'go-to-browse-brick';
// - Defaults
/** @var string DEFAULT_RULE_SUBMIT_PAGE */
const DEFAULT_RULE_SUBMIT_PAGE = self::ENUM_RULE_GO_TO_OBJECT;
/** @var string DEFAULT_RULE_SUBMIT_MODAL */
const DEFAULT_RULE_SUBMIT_MODAL = self::DEFAULT_RULE_SUBMIT_PAGE;
/** @var string DEFAULT_RULE_CANCEL_PAGE */
const DEFAULT_RULE_CANCEL_PAGE = self::ENUM_RULE_CLOSE;
/** @var string DEFAULT_RULE_CANCEL_MODAL */
const DEFAULT_RULE_CANCEL_MODAL = self::DEFAULT_RULE_CANCEL_PAGE;
// Rule go-to-object properties
/** @var string DEFAULT_RULE_GO_TO_OBJECT_PROP_MODE */
const DEFAULT_RULE_GO_TO_OBJECT_PROP_MODE = ObjectFormHandlerHelper::ENUM_MODE_VIEW;
/** @var string ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_MODAL */
const ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_MODAL = 'modal';
/** @var string ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_PAGE */
const ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_PAGE = 'page';
/** @var string ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_CURRENT */
const ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_CURRENT = 'current';
/** @var string DEFAULT_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET */
const DEFAULT_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET = self::ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_MODAL;
// Rule go-to-brick properties
// TODO
/** @var array $aRules */
protected $aRules;
/** @var \Symfony\Component\Routing\RouterInterface */
private $oRouter;
/** @var \Combodo\iTop\Portal\Brick\BrickCollection */
private $oBrickCollection;
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper */
private $oScopeValidator;
/**
* NavigationRuleHelper constructor.
*
* @param \ModuleDesign $oModuleDesign
* @param \Symfony\Component\Routing\RouterInterface $oRouter
* @param \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection
* @param \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeValidator
*
* @throws \DOMFormatException
*/
public function __construct(
ModuleDesign $oModuleDesign, RouterInterface $oRouter, BrickCollection $oBrickCollection, ScopeValidatorHelper $oScopeValidator
) {
$this->aRules = array();
$this->oRouter = $oRouter;
$this->oBrickCollection = $oBrickCollection;
$this->Init($oModuleDesign->GetNodes('/module_design/navigation_rules/navigation_rule'));
$this->oScopeValidator = $oScopeValidator;
}
/**
* Initializes the NavigationRuleHelper by caching rules in memory.
*
* @param \DOMNodeList $oNodes
*
* @throws \Exception
* @throws \DOMFormatException
*/
public function Init(DOMNodeList $oNodes)
{
$this->aRules = array();
// Iterating over the navigation_rule nodes
/** @var \Combodo\iTop\DesignElement $oRuleNode */
foreach ($oNodes as $oRuleNode)
{
// Checking node name
if ($oRuleNode->nodeName !== 'navigation_rule')
{
continue;
}
// Retrieving mandatory attributes
// - ID
$sRuleId = $oRuleNode->getAttribute('id');
if ($sRuleId === '')
{
throw new DOMFormatException('Rule tag must have an id attribute.', null, null, $oRuleNode);
}
// - Type
$sRuleType = $oRuleNode->getAttribute('xsi:type');
if (($sRuleType === '') || !in_array($sRuleType, static::GetAllowedTypes()))
{
throw new DOMFormatException('Navigation rule tag must have a valid xsi:type, "'.$sRuleType.'" given, expected '.implode('|',
static::GetAllowedTypes()), null, null, $oRuleNode);
}
// Load rule from XML
$sRuleLoadingFunction = 'Load'.utils::ToCamelCase($sRuleType).'RuleFromXML';
$this->aRules[$sRuleId] = $this->$sRuleLoadingFunction($oRuleNode);
}
}
//--------------------
// Enumeration helpers
//--------------------
/**
* Return an array of the allowed point of origin for the navigation
*
* @return array
*/
public static function GetAllowedOrigins()
{
return array(
static::ENUM_ORIGIN_PAGE,
static::ENUM_ORIGIN_MODAL,
);
}
/**
* Return an array of allowed navigation rule types (those in <navigation_rule xsi:type"XXX" />)
*
* @return array
*/
public static function GetAllowedTypes()
{
return array(
static::ENUM_RULE_CLOSE,
static::ENUM_RULE_GO_TO_HOMEPAGE,
static::ENUM_RULE_GO_TO_OBJECT,
static::ENUM_RULE_GO_TO_BRICK,
static::ENUM_RULE_GO_TO_BROWSE_BRICK,
static::ENUM_RULE_GO_TO_MANAGE_BRICK,
);
}
/**
* Return the definition of the rule identified by its ID, as a hash array
*
* @param string $sId
*
* @return array
* @throws \Exception
*/
public function GetRuleDefinition($sId)
{
if (!array_key_exists($sId, $this->aRules))
{
throw new Exception('NavigationRuleHelper: Could not find "'.$sId.'" in the rules list');
}
return $this->aRules[$sId];
}
/**
* Returns a hash array of ID => rule definition
*
* @return array
*/
public function GetRulesDefinitions()
{
return $this->aRules;
}
//-------------------------
// Default rules definition
//-------------------------
/**
* Return the default definition of the "Close" rule
*
* @return array
*/
public function GetDefaultCloseRuleDefinition()
{
return array(
'category' => static::ENUM_RULE_CAT_CLOSE,
'type' => static::ENUM_RULE_CLOSE,
);
}
/**
* Return the default definition of the "Go to homepage" rule
*
* @return array
*/
public function GetDefaultGoToHomepageRuleDefinition()
{
return array(
'category' => static::ENUM_RULE_CAT_REDIRECT,
'type' => static::ENUM_RULE_GO_TO_HOMEPAGE,
);
}
/**
* Return the default definition of the "Go to object" rule
*
* @return array
*/
public function GetDefaultGoToObjectRuleDefinition()
{
return array(
'category' => static::ENUM_RULE_CAT_REDIRECT,
'type' => static::ENUM_RULE_GO_TO_OBJECT,
'properties' => array(
'mode' => static::DEFAULT_RULE_GO_TO_OBJECT_PROP_MODE,
'opening_target' => static::DEFAULT_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET,
'oql' => null,
),
);
}
/**
* Return the default definition of the "Go to brick" rule
*
* @return array
*/
public function GetDefaultGoToBrickRuleDefinition()
{
return array(
'category' => static::ENUM_RULE_CAT_REDIRECT,
'type' => static::ENUM_RULE_GO_TO_BRICK,
'properties' => array(
'route' => array(
'id' => null,
'params' => array(),
),
),
);
}
//----------------------------
// Rules definition XML loader
//----------------------------
/**
* @noinspection PhpUnused Called dynamically by static::Init()
*
* @param \Combodo\iTop\DesignElement $oRuleNode
*
* @return array
*/
protected function LoadCloseRuleFromXML(DesignElement $oRuleNode)
{
// No special configuration needed
return $this->GetDefaultCloseRuleDefinition();
}
/**
* @noinspection PhpUnused Called dynamically by static::Init()
*
* @param \Combodo\iTop\DesignElement $oRuleNode
*
* @return array
*/
protected function LoadGoToHomepageRuleFromXML(DesignElement $oRuleNode)
{
// No special configuration needed
return $this->GetDefaultGoToHomepageRuleDefinition();
}
/**
* @noinspection PhpUnused Called dynamically by static::Init()
*
* @param \Combodo\iTop\DesignElement $oRuleNode
*
* @return array
* @throws \DOMFormatException
*/
protected function LoadGoToObjectRuleFromXML(DesignElement $oRuleNode)
{
$sRuleId = $oRuleNode->getAttribute('id');
// Default values
$aRule = $this->GetDefaultGoToObjectRuleDefinition();
$aAllowedOpeningTarget = array(
static::ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_CURRENT,
static::ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_MODAL,
static::ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_PAGE,
);
/** @var \Combodo\iTop\DesignElement $oPropNode */
foreach($oRuleNode->childNodes as $oPropNode)
{
switch($oPropNode->nodeName)
{
case 'mode':
$sMode = $oPropNode->GetText();
if(!in_array($sMode, ObjectFormHandlerHelper::GetAllowedModes()))
{
throw new DOMFormatException('mode tag of navigation_rule "'.$sRuleId.'" must be valid. Expected '.implode('|', ObjectFormHandlerHelper::GetAllowedModes()).', "'.$sMode.'" given.', null, null, $oRuleNode);
}
$aRule['properties']['mode'] = $sMode;
break;
case 'opening_target':
$sOpeningTarget = $oPropNode->GetText();
if(!in_array($sOpeningTarget, $aAllowedOpeningTarget))
{
throw new DOMFormatException('opening_target tag of navigation_rule "'.$sRuleId.'" must be valid. Expected '.implode('|', $aAllowedOpeningTarget).', "'.$sOpeningTarget.'" given.', null, null, $oRuleNode);
}
$aRule['properties']['opening_target'] = $sOpeningTarget;
break;
case 'oql':
$sOQL = $oPropNode->GetText();
if(empty($sOQL))
{
throw new DOMFormatException('oql tag of navigation_rule "'.$sRuleId.'" can not be empty.');
}
$aRule['properties']['oql'] = $sOQL;
break;
}
}
return $aRule;
}
/**
* @noinspection PhpUnused Called dynamically by static::Init()
*
* @param \Combodo\iTop\DesignElement $oRuleNode
*
* @return array
* @throws \DOMFormatException
*/
protected function LoadGoToBrickRuleFromXML(DesignElement $oRuleNode)
{
$sRuleId = $oRuleNode->getAttribute('id');
// Default values
$aRule = $this->GetDefaultGoToBrickRuleDefinition();
/** @var \Combodo\iTop\DesignElement $oPropNode */
foreach($oRuleNode->childNodes as $oPropNode)
{
switch($oPropNode->nodeName)
{
case 'route':
/** @var array $aRouteProperties Route ID and parameters */
$aRouteProperties = array();
/** @var DesignElement $oRoutePropNode */
foreach($oPropNode->childNodes as $oRoutePropNode)
{
switch($oRoutePropNode->nodeName)
{
case 'id':
$aRouteProperties['id'] = $oRoutePropNode->GetText();
break;
case 'params':
/** @var DesignElement $oRouteParamNode */
foreach($oRoutePropNode->childNodes as $oRouteParamNode)
{
$sRouteParamId = $oRouteParamNode->getAttribute('id');
$sRouteParamValue = $oRouteParamNode->GetText();
if(empty($sRouteParamId) || empty($sRouteParamValue))
{
throw new DOMFormatException('param tag of navigation_rule "'.$sRuleId.'" must have a valid ID and value.', null, null, $oRuleNode);
}
$aRouteProperties['params'][$sRouteParamId] = $sRouteParamValue;
}
break;
}
}
// Consistency check
if(empty($aRouteProperties['id']))
{
throw new DOMFormatException('navigation_rule "'.$sRuleId.'" must have a valid ID', null, null, $oRuleNode);
}
$aRule['properties']['route'] = $aRouteProperties;
break;
}
}
return $aRule;
}
/**
* @noinspection PhpUnused Called dynamically by static::Init()
*
* Load definition of a "go-to-manage-brick" rule from XML.
* This is a shortcut to a classic "go-to-brick" rule.
*
* @param \Combodo\iTop\DesignElement $oRuleNode
*
* @return array
*/
protected function LoadGoToManageBrickRuleFromXML(DesignElement $oRuleNode)
{
$sRuleId = $oRuleNode->getAttribute('id');
// Default values
$aRule = $this->GetDefaultGoToBrickRuleDefinition();
$aRule['properties']['route']['id'] = 'p_manage_brick_display_as';
// Rule parameters to automatically map to the route parameters
$aParamsMapping = array(
'id' => 'sBrickId',
'display_mode' => 'sDisplayMode',
'grouping_tab' => 'sGroupingTab',
'filter' => 'sSearchValue',
);
/** @var \Combodo\iTop\DesignElement $oPropNode */
foreach($oRuleNode->childNodes as $oPropNode)
{
$sRouteParamId = (array_key_exists($oPropNode->nodeName, $aParamsMapping)) ? $aParamsMapping[$oPropNode->nodeName] : $oPropNode->nodeName;
$aRule['properties']['route']['params'][$sRouteParamId] = $oPropNode->GetText();
}
return $aRule;
}
/**
* @noinspection PhpUnused Called dynamically by static::Init()
*
* Load definition of a "go-to-browse-brick" rule from XML.
* This is a shortcut to a classic "go-to-brick" rule.
*
* @param \Combodo\iTop\DesignElement $oRuleNode
*
* @return array
*/
protected function LoadGoToBrowseBrickRuleFromXML(DesignElement $oRuleNode)
{
$sRuleId = $oRuleNode->getAttribute('id');
// Default values
$aRule = $this->GetDefaultGoToBrickRuleDefinition();
$aRule['properties']['route']['id'] = 'p_browse_brick_mode';
// Rule parameters to automatically map to the route parameters
$aParamsMapping = array(
'id' => 'sBrickId',
'browse_mode' => 'sBrowseMode',
'filter' => 'sSearchValue',
);
/** @var \Combodo\iTop\DesignElement $oPropNode */
foreach($oRuleNode->childNodes as $oPropNode)
{
$sRouteParamId = (array_key_exists($oPropNode->nodeName, $aParamsMapping)) ? $aParamsMapping[$oPropNode->nodeName] : $oPropNode->nodeName;
$aRule['properties']['route']['params'][$sRouteParamId] = $oPropNode->GetText();
}
return $aRule;
}
//------------------------
// Business logic function
//------------------------
/**
* Returns a hash array containing the target URL and if it should be opened in a modal for each type of callbacks (submit and cancel)
*
* eg :
* array(
* 'submit' => array(
* 'type' => 'redirect',
* 'url' => 'http://localhost/',
* 'modal' => false,
* 'cancel' => array(
* 'type' => 'close',
* 'url' => null,
* 'modal' => false,
* )
* );
*
* @param array $aFormProperties
* @param \DBObject $oCurrentObject
* @param boolean $bIsCurrentFormInModal
*
* @return array
* @throws \Exception
*/
public function PrepareRulesForForm(array $aFormProperties, DBObject $oCurrentObject, $bIsCurrentFormInModal = false)
{
// Default values
$aResults = array(
'submit' => array(
'category' => static::ENUM_RULE_CAT_REDIRECT,
'url' => null,
'modal' => false,
),
'cancel' => array(
'category' => static::ENUM_RULE_CAT_CLOSE,
'url' => null,
'modal' => false,
),
);
// Get form's navigation rules
$aFormNavRules = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['navigation_rules'] : array('submit' => null, 'cancel' => null);
// Check from which origin the rule will be called
$sRuleCallOrigin = ($bIsCurrentFormInModal) ? 'modal' : 'default';
foreach(array_keys($aResults) as $sButtonCode)
{
// Retrieve rule definition
// - Default behavior when no rule specified
if(empty($aFormNavRules[$sButtonCode][$sRuleCallOrigin]))
{
switch($sButtonCode)
{
case 'submit':
$aRuleDef = $this->GetDefaultGoToObjectRuleDefinition();
break;
case 'cancel':
$aRuleDef = $this->GetDefaultCloseRuleDefinition();
break;
}
}
// - Specified rule
else
{
$sRuleId = $aFormNavRules[$sButtonCode][$sRuleCallOrigin];
$aRuleDef = $this->GetRuleDefinition($sRuleId);
}
// Set category
$aResults[$sButtonCode]['category'] = $aRuleDef['category'];
// Set properties regarding the type
switch($aRuleDef['type'])
{
case static::ENUM_RULE_GO_TO_HOMEPAGE:
$aResults[$sButtonCode]['url'] = $this->oRouter->generate('p_home');
break;
case static::ENUM_RULE_GO_TO_OBJECT:
// Target opening mode to modal if specified or should be as current form
if( ($aRuleDef['properties']['opening_target'] === static::ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_MODAL)
|| (($aRuleDef['properties']['opening_target'] === static::ENUM_RULE_GO_TO_OBJECT_PROP_OPENING_TARGET_CURRENT) && ($bIsCurrentFormInModal === true))
)
{
$aResults[$sButtonCode]['modal'] = true;
}
// Target URL
// - Find object
if(empty($aRuleDef['properties']['oql']))
{
$oTargetObject = $oCurrentObject;
}
else
{
$oSearch = DBSearch::FromOQL($aRuleDef['properties']['oql']);
$oSet = new DBObjectSet($oSearch, array(), array('this' => $oCurrentObject));
$oSet->OptimizeColumnLoad(array());
$oTargetObject = $oSet->Fetch();
}
// - Build URL
$aResults[$sButtonCode]['url'] = $this->oRouter->generate('p_object_'.$aRuleDef['properties']['mode'], array('sObjectClass' => get_class($oTargetObject), 'sObjectId' => $oTargetObject->GetKey()));
break;
case static::ENUM_RULE_GO_TO_BRICK:
// Build URL
$aRouteProperties = $aRuleDef['properties']['route'];
$aResults[$sButtonCode]['url'] = $this->oRouter->generate($aRouteProperties['id'], $aRouteProperties['params']);
break;
case static::ENUM_RULE_CLOSE:
default:
// Don't set the URL
break;
}
}
return $aResults;
}
}

View File

@@ -16,8 +16,6 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*
*
*/
@@ -63,6 +61,8 @@ class ObjectFormHandlerHelper
private $oRequestManipulator;
/** @var \Combodo\iTop\Portal\Helper\ContextManipulatorHelper */
private $oContextManipulator;
/** @var \Combodo\iTop\Portal\Helper\NavigationRuleHelper */
private $oNavigationRuleHelper;
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper */
private $oScopeValidator;
/** @var \Combodo\iTop\Portal\Helper\SecurityHelper */
@@ -83,6 +83,7 @@ class ObjectFormHandlerHelper
*
* @param \Combodo\iTop\Portal\Helper\RequestManipulatorHelper $oRequestManipulator
* @param \Combodo\iTop\Portal\Helper\ContextManipulatorHelper $oContextManipulator
* @param \Combodo\iTop\Portal\Helper\NavigationRuleHelper $oNavigationRuleHelper
* @param \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeValidator
* @param \Combodo\iTop\Portal\Helper\SecurityHelper $oSecurityHelper
* @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $oUrlGenerator
@@ -91,10 +92,11 @@ class ObjectFormHandlerHelper
* @param \Combodo\iTop\Portal\Twig\AppExtension $oAppExtension
* @param \Symfony\Component\DependencyInjection\ContainerInterface $oContainer
*/
public function __construct(RequestManipulatorHelper $oRequestManipulator, ContextManipulatorHelper $oContextManipulator, ScopeValidatorHelper $oScopeValidator, SecurityHelper $oSecurityHelper, UrlGeneratorInterface $oUrlGenerator, $aCombodoPortalInstanceConf, $sPortalId, AppExtension $oAppExtension, ContainerInterface $oContainer)
public function __construct(RequestManipulatorHelper $oRequestManipulator, ContextManipulatorHelper $oContextManipulator, NavigationRuleHelper $oNavigationRuleHelper, ScopeValidatorHelper $oScopeValidator, SecurityHelper $oSecurityHelper, UrlGeneratorInterface $oUrlGenerator, $aCombodoPortalInstanceConf, $sPortalId, AppExtension $oAppExtension, ContainerInterface $oContainer)
{
$this->oRequestManipulator = $oRequestManipulator;
$this->oContextManipulator = $oContextManipulator;
$this->oNavigationRuleHelper = $oNavigationRuleHelper;
$this->oScopeValidator = $oScopeValidator;
$this->oSecurityHelper = $oSecurityHelper;
$this->oUrlGenerator = $oUrlGenerator;
@@ -222,10 +224,13 @@ class ObjectFormHandlerHelper
$oObject->PrefillForm('state_change', $aPrefillFormParam);
}
// Preparing callback urls
$aCallbackUrls = $this->oContextManipulator->GetCallbackUrls($aActionRules, $oObject, $bModal);
$aFormData['submit_callback'] = $aCallbackUrls['submit'];
$aFormData['cancel_callback'] = $aCallbackUrls['cancel'];
// Preparing navigation rules
$aNavigationRules = $this->oNavigationRuleHelper->PrepareRulesForForm($aFormProperties, $oObject, $bModal);
$aFormData['submit_rule'] = $aNavigationRules['submit'];
$aFormData['cancel_rule'] = $aNavigationRules['cancel'];
/** @deprecated We keep the "xxx_callback" name to keep compatibility with extensions using the portal_form_handler.js widget but they will be removed in a future version. */
$aFormData['submit_callback'] = $aNavigationRules['submit']['url'];
$aFormData['cancel_callback'] = $aNavigationRules['cancel']['url'];
// Preparing renderer
// Note : We might need to distinguish form & renderer endpoints
@@ -292,23 +297,16 @@ class ObjectFormHandlerHelper
);
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
// 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 serialized
// Check if stimulus has to be applied
$sStimulusCode = $this->oRequestManipulator->ReadParam('stimulus_code', '');
if (!empty($sStimulusCode))
{
$aFormData['validation']['redirection'] = array(
'url' => $this->oUrlGenerator->generate('p_object_apply_stimulus', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey(), 'sStimulusCode' => $sStimulusCode)),
'ajax' => true,
'modal' => true,
);
}
// Otherwise, we show the object if there is no default
// else
// {
// $aFormData['validation']['redirection'] = array(
// 'alternative_url' => $this->oUrlGenerator->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
// );
// }
}
break;
@@ -410,4 +408,19 @@ class ObjectFormHandlerHelper
return $oTwig->render($sId, $aData);
}
/**
* Return an array of the available modes for a form.
*
* @since 2.7.0
* @return array
*/
public static function GetAllowedModes()
{
return array(
static::ENUM_MODE_VIEW,
static::ENUM_MODE_EDIT,
static::ENUM_MODE_CREATE,
);
}
}

View File

@@ -76,8 +76,8 @@
field_set: oFieldSet_{{ sFormIdSanitized }},
submit_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_submit, .form_btn_transition'),
cancel_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_cancel'),
submit_url: {% if form.submit_callback is not null %}"{{ form.submit_callback|raw }}"{% else %}null{% endif %},
cancel_url: {% if form.cancel_callback is not null %}"{{ form.cancel_callback|raw }}"{% else %}null{% endif %},
{% if form.submit_rule is not null %}submit_rule: {{ form.submit_rule|json_encode|raw }}{% endif %},
{% if form.cancel_rule is not null %}cancel_rule: {{ form.cancel_rule|json_encode|raw }}{% endif %},
endpoint: "{{ form.renderer.GetEndpoint()|raw }}",
is_modal: {% if tIsModal == true %}true{% else %}false{% endif %}
});

View File

@@ -1,13 +1,21 @@
<?php
/*
* This file is part of Composer.
/**
* Copyright (C) 2013-2019 Combodo SARL
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
* This file is part of iTop.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
* 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
*/
namespace Composer\Autoload;
@@ -279,7 +287,7 @@ class ClassLoader
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**

View File

@@ -1,4 +1,21 @@
<?php
/**
* Copyright (C) 2013-2019 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
*/
// autoload_classmap.php @generated by Composer
@@ -30,8 +47,8 @@ return array(
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Basic' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Forms' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Lists' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => $baseDir . '/src/EventListener/ApplicationContextSetUrlMakerClass.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetPluginPropertyClass' => $baseDir . '/src/EventListener/ApplicationContextSetPluginPropertyClass.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => $baseDir . '/src/EventListener/ApplicationContextSetUrlMakerClass.php',
'Combodo\\iTop\\Portal\\EventListener\\UserProvider' => $baseDir . '/src/EventListener/UserProvider.php',
'Combodo\\iTop\\Portal\\Form\\ObjectFormManager' => $baseDir . '/src/Form/ObjectFormManager.php',
'Combodo\\iTop\\Portal\\Form\\PasswordFormManager' => $baseDir . '/src/Form/PasswordFormManager.php',
@@ -41,6 +58,7 @@ return array(
'Combodo\\iTop\\Portal\\Helper\\BrowseBrickHelper' => $baseDir . '/src/Helper/BrowseBrickHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ContextManipulatorHelper' => $baseDir . '/src/Helper/ContextManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\LifecycleValidatorHelper' => $baseDir . '/src/Helper/LifecycleValidatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\NavigationRuleHelper' => $baseDir . '/src/Helper/NavigationRuleHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ObjectFormHandlerHelper' => $baseDir . '/src/Helper/ObjectFormHandlerHelper.php',
'Combodo\\iTop\\Portal\\Helper\\RequestManipulatorHelper' => $baseDir . '/src/Helper/RequestManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ScopeValidatorHelper' => $baseDir . '/src/Helper/ScopeValidatorHelper.php',

View File

@@ -1,4 +1,21 @@
<?php
/**
* Copyright (C) 2013-2019 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
*/
// autoload_static.php @generated by Composer
@@ -50,8 +67,8 @@ class ComposerStaticInitdf408f3f8ea034d298269cdf7647358b
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Basic' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Forms' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Lists' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetUrlMakerClass.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetPluginPropertyClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetPluginPropertyClass.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetUrlMakerClass.php',
'Combodo\\iTop\\Portal\\EventListener\\UserProvider' => __DIR__ . '/../..' . '/src/EventListener/UserProvider.php',
'Combodo\\iTop\\Portal\\Form\\ObjectFormManager' => __DIR__ . '/../..' . '/src/Form/ObjectFormManager.php',
'Combodo\\iTop\\Portal\\Form\\PasswordFormManager' => __DIR__ . '/../..' . '/src/Form/PasswordFormManager.php',
@@ -61,6 +78,7 @@ class ComposerStaticInitdf408f3f8ea034d298269cdf7647358b
'Combodo\\iTop\\Portal\\Helper\\BrowseBrickHelper' => __DIR__ . '/../..' . '/src/Helper/BrowseBrickHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ContextManipulatorHelper' => __DIR__ . '/../..' . '/src/Helper/ContextManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\LifecycleValidatorHelper' => __DIR__ . '/../..' . '/src/Helper/LifecycleValidatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\NavigationRuleHelper' => __DIR__ . '/../..' . '/src/Helper/NavigationRuleHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ObjectFormHandlerHelper' => __DIR__ . '/../..' . '/src/Helper/ObjectFormHandlerHelper.php',
'Combodo\\iTop\\Portal\\Helper\\RequestManipulatorHelper' => __DIR__ . '/../..' . '/src/Helper/RequestManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ScopeValidatorHelper' => __DIR__ . '/../..' . '/src/Helper/ScopeValidatorHelper.php',