N°952 Portal: Added UI extension APIs similar to those used in the console (Experimental!)

SVN:trunk[4852]
This commit is contained in:
Guillaume Lajarige
2017-08-02 11:30:30 +00:00
parent 24430e630f
commit 6f40459215
18 changed files with 477 additions and 74 deletions

View File

@@ -308,12 +308,18 @@ interface iPopupMenuExtension
* $param is null
*/
const MENU_USER_ACTIONS = 5;
/**
* Insert an item into the Action menu on an object item in an objects list in the portal
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on the current line)
*/
const PORTAL_OBJLISTITEM_ACTIONS = 7;
/**
* Insert an item into the Action menu on an object details page in the portal
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed)
*/
const PORTAL_OBJDETAILS_ACTIONS = 7;
const PORTAL_OBJDETAILS_ACTIONS = 8;
/**
* Insert an item into the Actions menu of a list in the portal
@@ -330,7 +336,7 @@ interface iPopupMenuExtension
* $param is the portal id
* @todo
*/
const PORTAL_USER_ACTIONS = 8;
const PORTAL_USER_ACTIONS = 9;
/**
* Insert an item into the navigation menu of the portal
* Note: This is not implemented yet !
@@ -338,7 +344,7 @@ interface iPopupMenuExtension
* $param is the portal id
* @todo
*/
const PORTAL_MENU_ACTIONS = 9;
const PORTAL_MENU_ACTIONS = 10;
/**
* Get the list of items to be added to a menu.
@@ -616,6 +622,128 @@ interface iPageUIExtension
public function GetBannerHtml(iTopWebPage $oPage);
}
/**
* Implement this interface to add content to any enhanced portal page
*
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
*
* @package Extensibility
* @api
* @since 2.4
*/
interface iPortalUIExtension
{
const ENUM_PORTAL_EXT_UI_BODY = 'Body';
const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu';
const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent';
/**
* Returns an array of CSS file urls
*
* @param \Silex\Application $oApp
* @return array
*/
public function GetCSSFiles(\Silex\Application $oApp);
/**
* Returns inline (raw) CSS
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetCSSInline(\Silex\Application $oApp);
/**
* Returns an array of JS file urls
*
* @param \Silex\Application $oApp
* @return array
*/
public function GetJSFiles(\Silex\Application $oApp);
/**
* Returns raw JS code
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetJSInline(\Silex\Application $oApp);
/**
* Returns raw HTML code to put at the end of the <body> tag
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetBodyHTML(\Silex\Application $oApp);
/**
* Returns raw HTML code to put at the end of the #main-wrapper element
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetMainContentHTML(\Silex\Application $oApp);
/**
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetNavigationMenuHTML(\Silex\Application $oApp);
}
/**
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
*/
abstract class AbstractPortalUIExtension implements iPortalUIExtension
{
/**
* @inheritDoc
*/
public function GetCSSFiles(\Silex\Application $oApp)
{
return array();
}
/**
* @inheritDoc
*/
public function GetCSSInline(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetJSFiles(\Silex\Application $oApp)
{
return array();
}
/**
* @inheritDoc
*/
public function GetJSInline(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetBodyHTML(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetMainContentHTML(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetNavigationMenuHTML(\Silex\Application $oApp)
{
return null;
}
}
/**
* Implement this interface to add new operations to the REST/JSON web service
*

View File

@@ -1718,7 +1718,7 @@ abstract class MetaModel
// Build the list of available extensions
//
$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension');
$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension');
foreach($aInterfaces as $sInterface)
{
self::$m_aExtensionClasses[$sInterface] = array();

View File

@@ -106,6 +106,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Brick:Portal:Manage:Name' => 'Spravovat položky',
'Brick:Portal:Manage:Table:NoData' => 'Žádná položka',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
));
// ObjectBrick brick

View File

@@ -98,9 +98,11 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Browse:Filter:NoData' => 'Kein Eintrag',
));
// ManageBrick brick
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Manage:Name' => 'Einträge managen',
'Brick:Portal:Manage:Table:NoData' => 'Kein Eintrag.',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
));
// ObjectBrick brick

View File

@@ -102,6 +102,7 @@ Dict::Add('EN US', 'English', 'English', array(
Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Manage:Name' => 'Manage items',
'Brick:Portal:Manage:Table:NoData' => 'No item.',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions',
));
// ObjectBrick brick

View File

@@ -102,6 +102,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:Manage:Name' => 'Administrar elementos',
'Brick:Portal:Manage:Table:NoData' => 'Sin objeto.',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
));
// ObjectBrick brick

View File

@@ -102,6 +102,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Manage:Name' => 'Gestion d\'éléments',
'Brick:Portal:Manage:Table:NoData' => 'Aucun élément',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions',
));
// ObjectBrick brick

View File

@@ -17,7 +17,7 @@ SetupWebPage::AddModule(
'portal/src/entities/portalbrick.class.inc.php',
'portal/src/controllers/abstractcontroller.class.inc.php',
'portal/src/controllers/brickcontroller.class.inc.php',
'portal/src/routers/abstractrouter.class.inc.php',
'portal/src/routers/abstractrouter.class.inc.php',
),
'webservice' => array(
//'webservices.itop-portal-base.php',
@@ -40,4 +40,4 @@ SetupWebPage::AddModule(
),
)
);
?>

View File

@@ -96,6 +96,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Brick:Portal:Manage:Name' => 'Beheer items',
'Brick:Portal:Manage:Table:NoData' => 'Geen gegevens',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
));
// ObjectBrick brick

View File

@@ -41,6 +41,9 @@ use \VariableExpression;
use \SQLExpression;
use \UnaryExpression;
use \Dict;
use \iPopupMenuExtension;
use \URLButtonItem;
use \JSButtonItem;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Helper\SecurityHelper;
use \Combodo\iTop\Portal\Brick\AbstractBrick;
@@ -381,6 +384,7 @@ class ManageBrickController extends BrickController
// Retrieving and preparing data for rendering
$aGroupingAreasData = array();
$bHasObjectListItemExtension = false;
foreach ($aSets as $sKey => $oSet)
{
// Set properties
@@ -402,7 +406,6 @@ class ManageBrickController extends BrickController
// Getting items
$aItems = array();
$aItemsIds = array();
// ... For each item
/** @var DBObject $oCurrentRow */
while ($oCurrentRow = $oSet->Fetch())
@@ -433,12 +436,12 @@ class ManageBrickController extends BrickController
// - Then set allowed action
if ($sActionType !== null)
{
$aActions[] = array(
'type' => $sActionType,
'class' => $sCurrentClass,
'id' => $oCurrentRow->GetKey(),
$aActions[] = array(
'type' => $sActionType,
'class' => $sCurrentClass,
'id' => $oCurrentRow->GetKey(),
'opening_target' => $oBrick->GetOpeningTarget(),
);
);
}
}
@@ -479,25 +482,50 @@ class ManageBrickController extends BrickController
'actions' => $aActions
);
}
// ... Checking menu extensions
$aItemButtons = array();
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
{
foreach($oExtensionInstance->EnumItems(iPopupMenuExtension::PORTAL_OBJLISTITEM_ACTIONS, array('portal_id' => $oApp['combodo.portal.instance.id'], 'object' => $oCurrentRow)) as $oMenuItem)
{
if (is_object($oMenuItem))
{
if($oMenuItem instanceof JSButtonItem)
{
$aItemButtons[] = $oMenuItem->GetMenuItem() + array('js_files' => $oMenuItem->GetLinkedScripts(), 'type' => 'button');
}
elseif($oMenuItem instanceof URLButtonItem)
{
$aItemButtons[] = $oMenuItem->GetMenuItem() + array('type' => 'link');
}
}
}
}
// ... And item's properties
$aItems[] = array(
'id' => $oCurrentRow->GetKey(),
'class' => $sCurrentClass,
'attributes' => $aItemAttrs,
'highlight_class' => $oCurrentRow->GetHilightClass()
'highlight_class' => $oCurrentRow->GetHilightClass(),
'actions' => $aItemButtons,
);
$aItemsIds = $oCurrentRow->GetKey();
if(!empty($aItemButtons))
{
$bHasObjectListItemExtension = true;
}
}
// Now that we retrieved items, we check which can be edited, which can be view and which cannot be opened
//
// Note: Now that we do checks here and not through the SecurityHelper while fetching objects, we might bypass datamodel security regarding the object class!
// $oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sCurrentClass, UR_ACTION_MODIFY);
// if($oSearchEditableItems !== null)
// {
// $oSearchEditableItems->A
// }
// Adding an extra column for object list item extensions
if($bHasObjectListItemExtension === true)
{
$aColumnsDefinition['_ui_extensions'] = array(
'title' => Dict::S('Brick:Portal:Manage:Table:ItemActions'),
'type' => 'html',
);
}
$aGroupingAreasData[$sKey] = array(
'sId' => $sKey,

View File

@@ -30,11 +30,13 @@ use \Dict;
use \utils;
use \IssueLog;
use \UserRights;
use \CMDBSource;
use \DOMFormatException;
use \ModuleDesign;
use \MetaModel;
use \DBObjectSearch;
use \DBObjectSet;
use \iPortalUIExtension;
use \Combodo\iTop\Portal\Brick\AbstractBrick;
/**
@@ -200,7 +202,7 @@ class ApplicationHelper
*/
static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv)
{
// A filter to translate a string via the Dict::S function
// Filter to translate a string via the Dict::S function
// Usage in twig : {{ 'String:ToTranslate'|dict_s }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s', function($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
@@ -208,7 +210,7 @@ class ApplicationHelper
})
);
// A filter to format a string via the Dict::Format function
// Filter to format a string via the Dict::Format function
// Usage in twig : {{ 'String:ToTranslate'|dict_format() }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format', function($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null)
{
@@ -216,12 +218,12 @@ class ApplicationHelper
})
);
// Filters to enable base64 encode/decode
// Filter to enable base64 encode/decode
// Usage in twig : {{ 'String to encode'|base64_encode }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
$oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
// Filters to enable json decode (encode already exists)
// Filter to enable json decode (encode already exists)
// Usage in twig : {{ aSomeArray|json_decode }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function($sJsonString, $bAssoc = false)
{
@@ -243,6 +245,22 @@ class ApplicationHelper
return $sUrl;
}));
// Filter to add a module's version to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function($sUrl, $sModuleName){
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl . "?moduleversion=" . $sModuleVersion;
}
else
{
$sUrl = $sUrl . "&moduleversion=" . $sModuleVersion;
}
return $sUrl;
}));
}
/**
@@ -398,8 +416,15 @@ class ApplicationHelper
),
'portals' => array(),
'forms' => array(),
'ui_extensions' => array(
'css_files' => array(),
'css_inline' => null,
'js_files' => array(),
'js_inline' => null,
'html' => array(),
),
'bricks' => array(),
'bricks_total_width' => 0
'bricks_total_width' => 0,
);
// - Global portal properties
foreach ($oDesign->GetNodes('/module_design/properties/*') as $oPropertyNode)
@@ -510,6 +535,8 @@ class ApplicationHelper
static::LoadLifecycleConfiguration($oApp, $oDesign);
// - Presentation lists
$aPortalConf['lists'] = static::LoadListsConfiguration($oApp, $oDesign);
// - UI extensions
$aPortalConf['ui_extensions'] = static::LoadUIExtensions($oApp);
// - Action rules
static::LoadActionRulesConfiguration($oApp, $oDesign);
// - Generating CSS files
@@ -1242,4 +1269,68 @@ class ApplicationHelper
return $aClassesLists;
}
/**
* Loads portal UI extensions
*
* @param \Silex\Application $oApp
* @return array
*/
static protected function LoadUIExtensions(Application $oApp)
{
$aUIExtensions = array(
'css_files' => array(),
'css_inline' => null,
'js_files' => array(),
'js_inline' => null,
'html' => array(),
);
$aUIExtensionHooks = array(
iPortalUIExtension::ENUM_PORTAL_EXT_UI_BODY,
iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU,
iPortalUIExtension::ENUM_PORTAL_EXT_UI_MAIN_CONTENT,
);
/** @var iPortalUIExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iPortalUIExtension') as $oExtensionInstance)
{
// Adding CSS files
$aUIExtensions['css_files'] = array_merge($aUIExtensions['css_files'], $oExtensionInstance->GetCSSFiles($oApp));
// Adding CSS inline
$sCSSInline = $oExtensionInstance->GetCSSInline($oApp);
if($sCSSInline !== null)
{
$aUIExtensions['css_inline'] .= "\n\n" . $sCSSInline;
}
// Adding JS files
$aUIExtensions['js_files'] = array_merge($aUIExtensions['js_files'], $oExtensionInstance->GetJSFiles($oApp));
// Adding JS inline
$sJSInline = $oExtensionInstance->GetJSInline($oApp);
if($sJSInline !== null)
{
// Note: Semi-colon is to prevent previous script that would have omitted it.
$aUIExtensions['js_inline'] .= "\n\n;\n" . $sJSInline;
}
// Adding HTML for each hook
foreach($aUIExtensionHooks as $sUIExtensionHook)
{
$sFunctionName = 'Get'.$sUIExtensionHook.'HTML';
$sHTML = $oExtensionInstance->$sFunctionName($oApp);
if($sHTML !== null)
{
if(!array_key_exists($sUIExtensionHook, $aUIExtensions['html']))
{
$aUIExtensions['html'][$sUIExtensionHook] = '';
}
$aUIExtensions['html'][$sUIExtensionHook] .= "\n\n" . $sHTML;
}
}
}
return $aUIExtensions;
}
}

View File

@@ -107,54 +107,130 @@
for(key in tableProperties)
{
columnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes."+key+".att_code",
"render": function(att_code, type, row){
var cellElem;
var itemActions;
var itemPrimarayAction;
// Preparing action on the cell
// Note : For now we will use only one action, the secondary actions are therefore not implemented. Only the data structure is done.
itemActions = row.attributes[att_code].actions;
// Preparing the cell data
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.html(row.attributes[att_code].value);
// Building actions
if(itemActions.length > 0)
{
// - Primary action
itemPrimaryAction = itemActions[0];
switch(itemPrimaryAction.type)
// Regular attribute columns
if(key !== '_ui_extensions') {
columnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes." + key + ".att_code",
"render": function (att_code, type, row) {
var cellElem;
var itemActions;
var itemPrimarayAction;
// Preparing action on the cell
// Note : For now we will use only one action, the secondary actions are therefore not implemented. Only the data structure is done.
itemActions = row.attributes[att_code].actions;
// Preparing the cell data
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.html(row.attributes[att_code].value);
// Building actions
if (itemActions.length > 0) {
// - Primary action
itemPrimaryAction = itemActions[0];
switch (itemPrimaryAction.type) {
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
default:
url = '#';
//console.log('Action "'+itemPrimaryAction+'" not implemented');
break;
}
SetActionUrl(cellElem, url);
SetActionOpeningTarget(cellElem, itemPrimaryAction.opening_target);
// - Secondary actions
// Not done for now, only the data structure is ready in case we need it later
}
return cellElem.prop('outerHTML');
},
});
}
// UI extensions buttons
else
{
columnsDefinition.push({
"width": "auto",
"searchable": false,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes." + key + ".att_code",
"render": function (att_code, type, row) {
var cellElem = $('<div class="group-actions-wrapper"></div>');
var actionsCount = row.actions.length;
// Adding menu wrapper in case there are several actions
var actionsElem = $('<div></div>');
actionsElem.appendTo(cellElem);
if(actionsCount > 1) {
actionsElem.addClass('group-actions pull-right');
// Adding hamburger icon toggler
actionsElem.append(
$('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-' + row.id + '"></a>')
);
// Adding sub menu
var actionsSSMenuElem = $('<div id="item-actions-menu-' + row.id + '" class="item-action-wrapper panel panel-default"></div>')
.appendTo(actionsElem);
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>')
.appendTo(actionsSSMenuElem);
}
// Adding actions
for(var i in row.actions)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
default:
url = '#';
//console.log('Action "'+itemPrimaryAction+'" not implemented');
break;
var actionDef = row.actions[i];
var actionElem = $('<a></a>')
.attr('href', actionDef.url)
.append( $('<span></span>').html(actionDef.label) );
// Adding css classes to action
for(var j in actionDef.css_classes)
{
actionElem.addClass(actionDef.css_classes[j]);
}
// Performing specific treatment regarding the action type
if(actionDef.type === 'button')
{
// External files
// Note: Not supported yet
// On click callback
actionElem.attr('onclick', actionDef.onclick);
}
else if(actionDef.type === 'link')
{
actionElem.attr('target', actionDef.target);
}
if(actionsCount > 1)
{
actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem) );
}
else
{
actionsElem.append( actionElem );
}
}
SetActionUrl(cellElem, url);
SetActionOpeningTarget(cellElem, itemPrimaryAction.opening_target);
// - Secondary actions
// Not done for now, only the data structure is ready in case we need it later
return cellElem.prop('outerHTML');
}
return cellElem.prop('outerHTML');
},
});
});
}
}
return columnsDefinition;
@@ -275,6 +351,11 @@
}
});
{% endfor %}
// Auto collapse item actions popup
$('body').click(function(){
$('table .item-action-wrapper.collapse.in').collapse('hide');
});
});
</script>
{% endblock %}

View File

@@ -23,7 +23,7 @@
{% if bGroupButtons == true %}
<li>
{% endif %}
<a class="{{ sButtonCssClasses }} {{ aButton.css_classes|join(' ') }}" href="{{ aButton.url }}" onclick="{{ aButton.onclick }}">{{ aButton.label }}</a>
<a class="{{ sButtonCssClasses }} {{ aButton.css_classes|join(' ') }}" href="{{ aButton.url }}" onclick="{{ aButton.onclick }}">{{ aButton.label|raw }}</a>
{% if bGroupButtons == true %}
</li>
{% endif %}
@@ -37,7 +37,7 @@
{% if bGroupButtons == true %}
<li>
{% endif %}
<a class="{{ sButtonCssClasses }} {{ aButton.css_classes|join(' ') }}" href="{{ aButton.url }}" target="{{ aButton.target }}">{{ aButton.label }}</a>
<a class="{{ sButtonCssClasses }} {{ aButton.css_classes|join(' ') }}" href="{{ aButton.url }}" target="{{ aButton.target }}">{{ aButton.label|raw }}</a>
{% if bGroupButtons == true %}
</li>
{% endif %}

View File

@@ -24,6 +24,7 @@
{% endblock %}
<title>{% block pPageTitle %}{% if sPageTitle is defined and sPageTitle is not null %}{{ sPageTitle }} - {{ constant('ITOP_APPLICATION') }}{% else %}{{ 'Page:DefaultTitle'|dict_s }}{% endif %}{% endblock %}</title>
<link rel="shortcut icon" href="{{ app['combodo.absolute_url'] ~ 'images/favicon.ico'|add_itop_version }}" />
{% block pPageStylesheets %}
{# First bootstrap core, lib themes, then bootstrap theme, portal adjustements #}
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap/css/bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
@@ -47,6 +48,12 @@
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.bootstrap|add_itop_version }}" rel="stylesheet" id="css_bootstrap_theme">
{# - Portal adjustments for BS theme #}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.portal|add_itop_version }}" rel="stylesheet" id="css_portal">
{# UI Extensions CSS, in an undefined order #}
{% if app['combodo.portal.instance.conf'].ui_extensions.css_files is defined %}
{% for css_file in app['combodo.portal.instance.conf'].ui_extensions.css_files %}
<link href="{{ css_file|add_itop_version }}" rel="stylesheet">
{% endfor %}
{% endif %}
{# Custom CSS that is supposed to do adjustments to the portal #}
{% if app['combodo.portal.instance.conf'].properties.themes.custom is defined %}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom|add_itop_version }}" rel="stylesheet">
@@ -58,6 +65,16 @@
{% endfor %}
{% endif %}
{% endblock %}
{% block pStyleinline %}
{# UI Extensions inline CSS #}
{% if app['combodo.portal.instance.conf'].ui_extensions.css_inline is not null %}
<style>
{{ app['combodo.portal.instance.conf'].ui_extensions.css_inline|raw }}
</style>
{% endif %}
{% endblock %}
{% block pPageScripts %}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery/jquery-1.11.3.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-ui/jquery-ui-1.11.4.min.js'|add_itop_version }}"></script>
@@ -100,6 +117,12 @@
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_handler.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field_html.js'|add_itop_version }}"></script>
{# UI Extensions JS, in an undefined order #}
{% if app['combodo.portal.instance.conf'].ui_extensions.js_files is defined %}
{% for js_file in app['combodo.portal.instance.conf'].ui_extensions.js_files %}
<script type="text/javascript" src="{{ js_file|add_itop_version }}"></script>
{% endfor %}
{% endif %}
{% endblock %}
</head>
<body class="{% block pPageBodyClass %}{% endblock %}">
@@ -179,6 +202,12 @@
{% endif %}
</ul>
</div>
{% block pPageUIExtensionNavigationMenuTopbar %}
{% if app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU')] is defined %}
{{ app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU')]|raw }}
{% endif %}
{% endblock %}
</div>
</nav>
{% endblock %}
@@ -235,6 +264,13 @@
</ul>
{% endblock %}
</div>
{% block pPageUIExtensionNavigationMenuSidebar %}
{% if app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU')] is defined %}
{{ app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU')]|raw }}
{% endif %}
{% endblock %}
{% if app['combodo.portal.instance.conf'].properties.logo is not null %}
<div class="logo">
{% block pNavigationSideMenuLogo %}
@@ -268,6 +304,12 @@
</section>
</div>
</div>
{% block pPageUIExtensionMainContent %}
{% if app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_MAIN_CONTENT')] is defined %}
{{ app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_MAIN_CONTENT')]|raw }}
{% endif %}
{% endblock %}
</div>
{% endblock %}
@@ -313,6 +355,12 @@
</div>
</div>
{% endblock %}
{% block pPageUIExtensionBody %}
{% if app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_BODY')] is defined %}
{{ app['combodo.portal.instance.conf'].ui_extensions.html[constant('iPortalUIExtension::ENUM_PORTAL_EXT_UI_BODY')]|raw }}
{% endif %}
{% endblock %}
{% endblock %}
{% block pPageLiveScripts %}
@@ -410,5 +458,14 @@
});
</script>
{% endblock %}
{% block pPageExtensionsScripts %}
{# UI Extensions inline JS #}
{% if app['combodo.portal.instance.conf'].ui_extensions.js_inline is not null %}
<script type="text/javascript">
{{ app['combodo.portal.instance.conf'].ui_extensions.js_inline|raw }}
</script>
{% endif %}
{% endblock %}
</body>
</html>

View File

@@ -582,6 +582,9 @@ footer {
font-size: 0.8em;
}
/* Secondary actions */
.list-group-item-actions .group-actions-wrapper, .mosaic-group-item-actions .group-actions-wrapper, table .group-actions-wrapper {
text-align: center;
}
table .group-actions {
position: relative;
}

View File

@@ -612,6 +612,11 @@ footer{
}
/* Secondary actions */
.list-group-item-actions .group-actions-wrapper,
.mosaic-group-item-actions .group-actions-wrapper,
table .group-actions-wrapper{
text-align: center;
}
table .group-actions{
position: relative;
}

View File

@@ -31,6 +31,7 @@ require_once APPROOT . '/core/moduledesign.class.inc.php';
require_once APPROOT . '/application/loginwebpage.class.inc.php';
require_once APPROOT . '/sources/autoload.php';
// Portal
// Note: This could be prevented by adding namespaces to composer
require_once __DIR__ . '/../src/providers/urlgeneratorserviceprovider.class.inc.php';
require_once __DIR__ . '/../src/helpers/urlgeneratorhelper.class.inc.php';
require_once __DIR__ . '/../src/providers/contextmanipulatorserviceprovider.class.inc.php';
@@ -109,6 +110,7 @@ $oApp->before(function(Symfony\Component\HttpFoundation\Request $oRequest, Silex
$oApp['debug'] = $bDebug;
$oApp['combodo.current_environment'] = utils::GetCurrentEnvironment();
$oApp['combodo.absolute_url'] = utils::GetAbsoluteUrlAppRoot();
$oApp['combodo.modules.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment();
$oApp['combodo.portal.base.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.base.absolute_path'] = MODULESROOT . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.instance.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_MODULE_ID . '/';

View File

@@ -84,6 +84,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
Dict::Add('RU RU', 'Russian', 'Русский', array(
'Brick:Portal:Manage:Name' => 'Управление элементами',
'Brick:Portal:Manage:Table:NoData' => 'Нет элементов',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
));
// ObjectBrick brick