Merge branch 'support/2.7' into develop

This commit is contained in:
odain
2020-09-07 16:48:15 +02:00
24 changed files with 543 additions and 657 deletions

View File

@@ -1014,8 +1014,8 @@ table .group-actions {
/****************/
/* Filter brick */
/****************/
.tile.tile-filter-brick .tile_decoration .icon {
color: #da7014;
.tile.tile-filter-brick a.tile_decoration {
cursor: default;
}
.tile.tile-filter-brick .tile_filterbox .form-group:first-child {
width: 100%;

View File

@@ -1077,8 +1077,8 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
/****************/
/* Filter brick */
/****************/
.tile.tile-filter-brick .tile_decoration .icon {
color: $link-color;
.tile.tile-filter-brick a.tile_decoration {
cursor: default;
}
.tile.tile-filter-brick .tile_filterbox .form-group:first-child{

View File

@@ -84,8 +84,9 @@ class BrowseBrickController extends BrickController
$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : $oRequestManipulator->ReadParam('sDataLoading',
$oBrick->GetDataLoading());
// Getting search value
$sSearchValue = $oRequestManipulator->ReadParam('sSearchValue', '');
if (!empty($sSearchValue))
$sRawSearchValue = $oRequestManipulator->ReadParam('sSearchValue', '');
$sSearchValue = html_entity_decode($sRawSearchValue);
if (strlen($sSearchValue) > 0)
{
$sDataLoading = AbstractBrick::ENUM_DATA_LOADING_LAZY;
}
@@ -167,13 +168,16 @@ class BrowseBrickController extends BrickController
// Adding search clause
// Note : For know the search is naive and looks only for the exact match. It doesn't search for words separately
if (!empty($sSearchValue))
if (strlen($sSearchValue) > 0)
{
// - Cleaning the search value by exploding and trimming spaces
$aSearchValues = explode(' ', $sSearchValue);
array_walk($aSearchValues, function (&$sSearchValue /*, $sKey*/) {
trim($sSearchValue);
});
$aExplodedSearchValues = explode(' ', $sSearchValue);
$aSearchValues = [];
foreach ($aExplodedSearchValues as $sValue) {
if (strlen($sValue) > 0) {
$aSearchValues[] = $sValue;
}
}
// - Retrieving fields to search
$aSearchFields = array($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['name_att']);
@@ -257,7 +261,7 @@ class BrowseBrickController extends BrickController
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetSelectedClasses($aLevelsClasses);
if (!empty($sSearchValue))
if (strlen($sSearchValue) > 0)
{
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
$aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams();
@@ -452,7 +456,7 @@ class BrowseBrickController extends BrickController
'sBrickId' => $sBrickId,
'sBrowseMode' => $sBrowseMode,
'aBrowseButtons' => $aBrowseButtons,
'sSearchValue' => $sSearchValue,
'sSearchValue' => $sRawSearchValue,
'sDataLoading' => $sDataLoading,
'aItems' => json_encode($aItems),
'iItemsCount' => count($aItems),

View File

@@ -894,64 +894,50 @@ class ManageBrickController extends BrickController
$oRequestManipulator = $this->get('request_manipulator');
// Getting search value
$sSearchValue = $oRequestManipulator->ReadParam('sSearchValue', '');
$sRawSearchValue = trim($oRequestManipulator->ReadParam('sSearchValue', ''));
$sSearchValue = html_entity_decode($sRawSearchValue);
// - Adding search clause if necessary
// Note : This is a very naive search at the moment
if (!empty($sSearchValue))
{
if (strlen($sSearchValue) > 0) {
// Putting only valid attributes as one can define attributes of leaf classes in the brick definition (<fields>), but at this stage we are working on the abstract class.
// Note: This won't fix everything as the search will not be looking in all fields.
$aSearchListItems = array();
foreach ($aColumnsAttrs as $sColumnAttr)
{
// Skip invalid attcodes
if (!MetaModel::IsValidAttCode($sClass, $sColumnAttr))
{
$aSearchListItems = [];
foreach ($aColumnsAttrs as $sColumnAttr) {
// Skip invalid attCodes
if (!MetaModel::IsValidAttCode($sClass, $sColumnAttr)) {
continue;
}
// For external key, force search on the friendlyname instead of the ID.
// This should be addressed more globally with the bigger issue, see N°1970
$oAttDef = MetaModel::GetAttributeDef($sClass, $sColumnAttr);
if($oAttDef instanceof AttributeExternalKey)
{
if ($oAttDef instanceof AttributeExternalKey) {
$sColumnAttr .= '_friendlyname';
}
$aSearchListItems[] = $sColumnAttr;
}
$oFullBinExpr = null;
foreach ($aSearchListItems as $sSearchItemAttr)
{
$oBinExpr = new BinaryExpression(new FieldExpression($sSearchItemAttr, $oQuery->GetClassAlias()),
'LIKE', new VariableExpression('search_value'));
// At each iteration we build the complete expression for the search like ( (field1 LIKE %search%) OR (field2 LIKE %search%) OR (field3 LIKE %search%) ...)
if (is_null($oFullBinExpr))
{
$oFullBinExpr = $oBinExpr;
}
else
{
$oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oBinExpr);
if (preg_match('/^"(.*)"$/', $sSearchValue, $aMatches)) {
// The text is surrounded by double-quotes, remove the quotes and treat it as one single expression
$aSearchNeedles = [$aMatches[1]];
} else {
// Split the text on the blanks and treat this as a search for <word1> AND <word2> AND <word3>
$aExplodedSearchNeedles = explode(' ', $sSearchValue);
$aSearchNeedles = [];
foreach ($aExplodedSearchNeedles as $sValue) {
if (strlen($sValue) > 0) {
$aSearchNeedles[] = $sValue;
}
}
}
// Then add the complete expression to the query
if (!is_null($oFullBinExpr))
{
// - Adding expression to the query
$oQuery->AddConditionExpression($oFullBinExpr);
// - Setting expression parameters
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
$aQueryParams = $oQuery->GetInternalParams();
$aQueryParams['search_value'] = '%'.$sSearchValue.'%';
$oQuery->SetInternalParams($aQueryParams);
foreach ($aSearchNeedles as $sSearchWord) {
$oQuery->AddCondition_FullTextOnAttributes($aSearchListItems, $sSearchWord);
}
}
$aData['sSearchValue'] = $sSearchValue;
$aData['sSearchValue'] = $sRawSearchValue;
}
/**

View File

@@ -25,7 +25,6 @@ use Exception;
/**
* Class ItopExtensionsExtraRoutes
*
* @deprecated Compatibility layer for migrating brick's routes to iTop 2.7+
* @package Combodo\iTop\Portal\Routing
* @since 2.7.0
* @author Bruno Da Silva <bruno.dasilva@combodo.com>
@@ -39,8 +38,6 @@ class ItopExtensionsExtraRoutes
* @param array $extraRoutes
*
* @throws Exception
* @deprecated Since 2.7.0
*
*/
public static function AddRoutes($extraRoutes)
{

View File

@@ -44,140 +44,148 @@
"defaultContent": "",
"type": "html",
"data": oLevelsProperties[sKey].alias,
"render": function(data, type, row){
var cellElem;
var levelAltId = data.level_alias+'_'+data.id;
var levelActions;
var levelActionsKeys;
var drilldownActionIndex;
var levelPrimaryAction;
var url = '';
// Preparing actions on the cell
levelActions = oLevelsProperties[data.level_alias].actions;
// - Removing explicit (not default) drilldown action as it has no prupose on that browse mode
delete levelActions['{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}'];
// - Removing implciit (default) drilldown action
if( (levelActions['default'] !== undefined) && (levelActions['default'].type === '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}') )
{
delete levelActions['default'];
}
levelActionsKeys = Object.keys(levelActions);
// Preparing the cell data
cellElem = (levelActionsKeys.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.attr('data-item-id', data.id).attr('data-level-alias', data.level_alias);
"render":
{_: function(data, type, row){
var cellElem;
var levelAltId = data.level_alias+'_'+data.id;
var levelActions;
var levelActionsKeys;
var drilldownActionIndex;
var levelPrimaryAction;
var url = '';
// Building metadata
for(var sPropName in data.metadata)
{
var propValue = data.metadata[sPropName];
cellElem.attr('data-'+sPropName.replace('_', '-'), propValue);
}
// Building tooltip for the node
// We have to concatenate the HTML as we return the raw HTML of the cell. If we did a jQuery.insertAfter, the tooltip would not be returned.
// For the same reason, tooltip widget is created in "drawCallback" instead of here.
if( (data.tooltip !== undefined) && (data.tooltip !== ''))
{
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').html(data.name).prop('outerHTML') );
}
else
{
cellElem.html(data.name);
}
// Building actions
if(levelActionsKeys.length > 0)
{
// - Primary action (click on item)
levelPrimaryAction = levelActions[levelActionsKeys[0]];
switch(levelPrimaryAction.type)
// Preparing actions on the cell
levelActions = oLevelsProperties[data.level_alias].actions;
// - Removing explicit (not default) drilldown action as it has no prupose on that browse mode
delete levelActions['{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}'];
// - Removing implciit (default) drilldown action
if( (levelActions['default'] !== undefined) && (levelActions['default'].type === '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}') )
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = levelPrimaryAction.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
break;
default:
url = '#';
//console.log('Action "'+levelPrimaryAction.type+'" not implemented');
break;
delete levelActions['default'];
}
SetActionUrl(cellElem, url);
SetActionOpeningTarget(cellElem, levelPrimaryAction.opening_target);
levelActionsKeys = Object.keys(levelActions);
// - Secondary actions
if(levelActionsKeys.length > 1)
// Preparing the cell data
cellElem = (levelActionsKeys.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.attr('data-item-id', data.id).attr('data-level-alias', data.level_alias);
// Building metadata
for(var sPropName in data.metadata)
{
// Retrieving secondary action (Now we also display primary action)
var actionsButtons = {};
for(j = 0; j < levelActionsKeys.length; j++)
{
actionsButtons[levelActionsKeys[j]] = levelActions[levelActionsKeys[j]];
}
// Preparing secondary actions container
var actionsElem = $('<div></div>').addClass('pull-right group-actions');
cellElem.append(actionsElem);
// Preparing secondary actions
var actionsSSTogglerElem = $('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-'+levelAltId+'"></a>');
var actionsSSMenuElem = $('<div id="item-actions-menu-'+levelAltId+'" class="item-action-wrapper panel panel-default"></div>');
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>');
actionsSSMenuElem.append(actionsSSMenuContainerElem);
actionsElem.append(actionsSSTogglerElem);
actionsElem.append(actionsSSMenuElem);
var propValue = data.metadata[sPropName];
cellElem.attr('data-'+sPropName.replace('_', '-'), propValue);
}
// Adding secondary actions
for(j in actionsButtons)
// Building tooltip for the node
// We have to concatenate the HTML as we return the raw HTML of the cell. If we did a jQuery.insertAfter, the tooltip would not be returned.
// For the same reason, tooltip widget is created in "drawCallback" instead of here.
if( (data.tooltip !== undefined) && (data.tooltip !== ''))
{
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').html(data.name).prop('outerHTML') );
}
else
{
cellElem.html(data.name);
}
// Building actions
if(levelActionsKeys.length > 0)
{
// - Primary action (click on item)
levelPrimaryAction = levelActions[levelActionsKeys[0]];
switch(levelPrimaryAction.type)
{
var action = actionsButtons[j];
var actionElem = $('<a></a>');
var actionIconElem = $('<span></span>').appendTo(actionElem);
switch(action.type)
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = levelPrimaryAction.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
break;
default:
url = '#';
//console.log('Action "'+levelPrimaryAction.type+'" not implemented');
break;
}
SetActionUrl(cellElem, url);
SetActionOpeningTarget(cellElem, levelPrimaryAction.opening_target);
// - Secondary actions
if(levelActionsKeys.length > 1)
{
// Retrieving secondary action (Now we also display primary action)
var actionsButtons = {};
for(j = 0; j < levelActionsKeys.length; j++)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = action.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
break;
default:
url = '#';
//console.log('Action "'+action.type+'" not implemented for secondary action');
break;
actionsButtons[levelActionsKeys[j]] = levelActions[levelActionsKeys[j]];
}
SetActionUrl(actionElem, url);
SetActionOpeningTarget(actionElem, action.opening_target);
// Adding title if present
if(action.title !== undefined)
// Preparing secondary actions container
var actionsElem = $('<div></div>').addClass('pull-right group-actions');
cellElem.append(actionsElem);
// Preparing secondary actions
var actionsSSTogglerElem = $('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-'+levelAltId+'"></a>');
var actionsSSMenuElem = $('<div id="item-actions-menu-'+levelAltId+'" class="item-action-wrapper panel panel-default"></div>');
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>');
actionsSSMenuElem.append(actionsSSMenuContainerElem);
actionsElem.append(actionsSSTogglerElem);
actionsElem.append(actionsSSMenuElem);
// Adding secondary actions
for(j in actionsButtons)
{
actionElem.attr('title', action.title);
var action = actionsButtons[j];
var actionElem = $('<a></a>');
var actionIconElem = $('<span></span>').appendTo(actionElem);
switch(action.type)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = action.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
break;
default:
url = '#';
//console.log('Action "'+action.type+'" not implemented for secondary action');
break;
}
SetActionUrl(actionElem, url);
SetActionOpeningTarget(actionElem, action.opening_target);
// Adding title if present
if(action.title !== undefined)
{
actionElem.attr('title', action.title);
}
// Adding icon class if present
if(action.icon_class !== undefined)
{
actionIconElem.addClass(action.icon_class);
}
actionElem.append(action.title);
actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem) );
}
// Adding icon class if present
if(action.icon_class !== undefined)
{
actionIconElem.addClass(action.icon_class);
}
actionElem.append(action.title);
actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem) );
}
}
}
return cellElem.prop('outerHTML');
return cellElem.prop('outerHTML');
},
filter:function (data, type, row) {
return $.text($.parseHTML(data.name));
},
sort:function (data, type, row) {
return $.text($.parseHTML(data.name));
},
},
});
@@ -195,25 +203,29 @@
"defaultContent": "",
"type": "html",
"data": oLevelsProperties[sKey].alias+".fields."+oLevelsProperties[sKey].fields[i].code,
"render": function(data, type, row){
var cellElem = $('<span></span>');
// Building value and metadata
for(var sPropName in data)
"render":
{
var sPropValue = data[sPropName];
if(sPropName === 'value_html')
{
cellElem.html(sPropValue);
}
else
{
cellElem.attr('data-'+sPropName.replace('_', '-'), sPropValue);
}
}
_: function (data, type, row) {
var cellElem = $('<span></span>');
return cellElem.prop('outerHTML');
},
// Building value and metadata
for (var sPropName in data) {
var sPropValue = data[sPropName];
if (sPropName === 'value_html') {
cellElem.html(sPropValue);
} else {
cellElem.attr('data-' + sPropName.replace('_', '-'), sPropValue);
}
}
return cellElem.prop('outerHTML');
},
sort: function (data, type, row) {
return $.text($.parseHTML(data['value_html']));
},
filter: function (data, type, row) {
return $.text($.parseHTML(data['value_html']));
},
},
});
}
}

View File

@@ -10,10 +10,9 @@
<div class="col-xs-12 col-sm-{{ brick.GetWidth }}">
{% block pTileWrapper %}
<div class="tile tile-filter-brick" id="brick-{{ brick.GetId }}" data-brick-id="{{ brick.GetId }}">
<div class="tile_decoration">
<a href="#" onclick="return false;" class="tile_decoration">
<span class="icon {{ brick.GetDecorationClassHome }}"></span>
</div>
</a>
<div class="tile_body">
<div class="tile_title">{{ brick.GetTitleHome|dict_s }}</div>
{% if brick.HasDescription %}

View File

@@ -158,8 +158,11 @@
return cellElem.prop('outerHTML');
},
sort: function (attribute_code, type, row) {
return row.attributes[attribute_code].sort_value;
return row.attributes[attribute_code].sort_value;
},
filter: function (attribute_code, type, row) {
return $.text($.parseHTML(row.attributes[attribute_code]['value_html']));
},
},
});
}