Browse Source

Customer portal : Fixed some bugs and rectified some default configuration parameters
- Form, ExternalKey autocomplete & regular search
- Portal power user being able to see all its silo tickets
- Worked on the UI

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4118 a333f486-631f-4898-b8df-5754b55c2be0

glajarige 9 years ago
parent
commit
7cb61f4459
20 changed files with 216 additions and 117 deletions
  1. 22 6
      datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php
  2. 2 2
      datamodels/2.x/itop-portal-base/portal/src/entities/portalbrick.class.inc.php
  3. 2 1
      datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php
  4. 12 3
      datamodels/2.x/itop-portal-base/portal/src/helpers/applicationhelper.class.inc.php
  5. 4 8
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/layout.html.twig
  6. 9 0
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/mode_list.html.twig
  7. 9 7
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/layout.html.twig
  8. 17 10
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/manage/layout.html.twig
  9. 13 2
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_search_regular.html.twig
  10. 1 1
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig
  11. 3 3
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/tile.html.twig
  12. 1 1
      datamodels/2.x/itop-portal-base/portal/src/views/bricks/user-profile/layout.html.twig
  13. 1 1
      datamodels/2.x/itop-portal-base/portal/src/views/home/layout.html.twig
  14. 2 2
      datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig
  15. 84 64
      datamodels/2.x/itop-portal-base/portal/web/css/portal.css
  16. BIN
      datamodels/2.x/itop-portal-base/portal/web/fonts/Combodo.otf
  17. BIN
      datamodels/2.x/itop-portal-base/portal/web/fonts/Combodo.ttf
  18. BIN
      datamodels/2.x/itop-portal-base/portal/web/fonts/Combodo.woff
  19. 27 6
      datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml
  20. 7 0
      sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php

+ 22 - 6
datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php

@@ -395,7 +395,7 @@ class ObjectController extends AbstractController
 			// But it would not be a security issue as it only presets values in the form.
 			// But it would not be a security issue as it only presets values in the form.
 			$sActionRulesToken = $oRequest->get('ar_token');
 			$sActionRulesToken = $oRequest->get('ar_token');
 			$aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
 			$aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
-
+			
 			// Preparing object
 			// Preparing object
 			if ($sObjectId === null)
 			if ($sObjectId === null)
 			{
 			{
@@ -606,6 +606,14 @@ class ObjectController extends AbstractController
 		else
 		else
 		{
 		{
 			$oHostObject = MetaModel::NewObject($sHostObjectClass);
 			$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 = $oRequest->get('ar_token');
+			$aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
+			// Preparing object
+			$oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
 		}
 		}
 
 
 		// Building search query
 		// Building search query
@@ -617,7 +625,7 @@ class ObjectController extends AbstractController
 		// - Adding query condition
 		// - Adding query condition
 		$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
 		$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
 		// - Intersecting with scope constraints
 		// - Intersecting with scope constraints
-		$oSearch->Intersect($oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ));
+		$oSearch = $oSearch->Intersect($oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ));
 
 
 		// Retrieving results
 		// Retrieving results
 		// - Preparing object set
 		// - Preparing object set
@@ -628,7 +636,7 @@ class ObjectController extends AbstractController
 		// - Retrieving objects
 		// - Retrieving objects
 		while ($oItem = $oSet->Fetch())
 		while ($oItem = $oSet->Fetch())
 		{
 		{
-			$aData['results']['items'][] = array('id' => $oItem->GetKey(), 'name' => $oItem->GetName());
+			$aData['results']['items'][] = array('id' => $oItem->GetKey(), 'name' => html_entity_decode($oItem->GetName(), ENT_QUOTES, 'UTF-8'));
 			$aData['results']['count'] ++;
 			$aData['results']['count'] ++;
 		}
 		}
 
 
@@ -661,7 +669,8 @@ class ObjectController extends AbstractController
 			'sMode' => 'search_regular',
 			'sMode' => 'search_regular',
 			'sTargetAttCode' => $sTargetAttCode,
 			'sTargetAttCode' => $sTargetAttCode,
 			'sHostObjectClass' => $sHostObjectClass,
 			'sHostObjectClass' => $sHostObjectClass,
-			'sHostObjectId' => $sHostObjectId
+			'sHostObjectId' => $sHostObjectId,
+			'sActionRulesToken' => $oRequest->get('ar_token')
 		);
 		);
 
 
 		// Checking security layers
 		// Checking security layers
@@ -678,6 +687,13 @@ class ObjectController extends AbstractController
 		else
 		else
 		{
 		{
 			$oHostObject = MetaModel::NewObject($sHostObjectClass);
 			$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 = ($aData['sActionRulesToken'] !== null) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
+			// Preparing object
+			$oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
 		}
 		}
 
 
 		// Retrieving request parameters
 		// Retrieving request parameters
@@ -780,7 +796,7 @@ class ObjectController extends AbstractController
 		}
 		}
 
 
 		// - Intersecting with scope constraints
 		// - Intersecting with scope constraints
-		$oSearch->Intersect($oScopeSearch);
+		$oSearch = $oSearch->Intersect($oScopeSearch);
 
 
 		// Retrieving results
 		// Retrieving results
 		// - Preparing object set
 		// - Preparing object set
@@ -999,7 +1015,7 @@ class ObjectController extends AbstractController
 //			$aInternalParams['re_query'] = '%' . $sQuery . '%';
 //			$aInternalParams['re_query'] = '%' . $sQuery . '%';
 //		}
 //		}
 		// - Intersecting with scope constraints
 		// - Intersecting with scope constraints
-		$oSearch->Intersect($oScopeSearch);
+		$oSearch = $oSearch->Intersect($oScopeSearch);
 
 
 		// Retrieving results
 		// Retrieving results
 		// - Preparing object set
 		// - Preparing object set

+ 2 - 2
datamodels/2.x/itop-portal-base/portal/src/entities/portalbrick.class.inc.php

@@ -135,7 +135,7 @@ abstract class PortalBrick extends AbstractBrick
 	 */
 	 */
 	public function GetRankHome()
 	public function GetRankHome()
 	{
 	{
-		return $this->bRankHome;
+		return $this->fRankHome;
 	}
 	}
 
 
 	/**
 	/**
@@ -145,7 +145,7 @@ abstract class PortalBrick extends AbstractBrick
 	 */
 	 */
 	public function GetRankNavigationMenu()
 	public function GetRankNavigationMenu()
 	{
 	{
-		return $this->bRankNavigationMenu;
+		return $this->fRankNavigationMenu;
 	}
 	}
 
 
 	/**
 	/**

+ 2 - 1
datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php

@@ -487,7 +487,8 @@ class ObjectFormManager extends FormManager
 							$sSearchEndpoint = $this->oApp['url_generator']->generate('p_object_search_generic', array(
 							$sSearchEndpoint = $this->oApp['url_generator']->generate('p_object_search_generic', array(
 								'sTargetAttCode' => $oAttDef->GetCode(),
 								'sTargetAttCode' => $oAttDef->GetCode(),
 								'sHostObjectClass' => get_class($this->oObject),
 								'sHostObjectClass' => get_class($this->oObject),
-								'sHostObjectId' => ($this->oObject->IsNew()) ? null : $this->oObject->GetKey()
+								'sHostObjectId' => ($this->oObject->IsNew()) ? null : $this->oObject->GetKey(),
+								'ar_token' => $this->GetActionRulesToken(),
 							));
 							));
 							$oField->SetSearchEndpoint($sSearchEndpoint);
 							$oField->SetSearchEndpoint($sSearchEndpoint);
 						}
 						}

+ 12 - 3
datamodels/2.x/itop-portal-base/portal/src/helpers/applicationhelper.class.inc.php

@@ -619,11 +619,20 @@ class ApplicationHelper
 			}
 			}
 		}
 		}
 		// - Sorting bricks by rank
 		// - Sorting bricks by rank
-		usort($aPortalConf['bricks'], function($a, $b)
+		$aPortalConf['bricks_ordering'] = array();
+		//   - Home
+		$aPortalConf['bricks_ordering']['home'] = $aPortalConf['bricks'];
+		usort($aPortalConf['bricks_ordering']['home'], function($a, $b)
 		{
 		{
-			return $a->GetRank() > $b->GetRank();
+			return $a->GetRankHome() > $b->GetRankHome();
 		});
 		});
-		
+		//    - Navigation menu
+		$aPortalConf['bricks_ordering']['navigation_menu'] = $aPortalConf['bricks'];
+		usort($aPortalConf['bricks_ordering']['navigation_menu'], function($a, $b)
+		{
+			return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu();
+		});
+
 		return $aPortalConf;
 		return $aPortalConf;
 	}
 	}
 
 

+ 4 - 8
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/layout.html.twig

@@ -8,14 +8,10 @@
 
 
 {% block pMainHeaderActions %}
 {% block pMainHeaderActions %}
 	{% if aBrowseButtons|length > 1 %}
 	{% if aBrowseButtons|length > 1 %}
-		<div class="row">
-			<div class="col-sm-12">
-				<div class="btn-group btn-group-justified btn-group-sm">
-					{% for sBrowseButton in aBrowseButtons %}
-					<a href="{{ app.url_generator.generate('p_browse_brick_mode', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseButton}) }}" class="btn btn-default {% if sBrowseMode == sBrowseButton %}active{% endif %}">{{ ('Brick:Portal:Browse:Mode:'~sBrowseButton|capitalize)|dict_s }}</a>
-					{% endfor %}
-				</div>
-			</div>
+		<div class="btn-group btn-group-sm btn_group_explicit">
+			{% for sBrowseButton in aBrowseButtons %}
+			<a href="{{ app.url_generator.generate('p_browse_brick_mode', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseButton}) }}" class="btn btn-default {% if sBrowseMode == sBrowseButton %}active{% endif %}">{{ ('Brick:Portal:Browse:Mode:'~sBrowseButton|capitalize)|dict_s }}</a>
+			{% endfor %}
 		</div>
 		</div>
 	{% endif %}
 	{% endif %}
 {% endblock %}
 {% endblock %}

+ 9 - 0
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/mode_list.html.twig

@@ -241,6 +241,15 @@
 				"drawCallback": function(settings){
 				"drawCallback": function(settings){
 					// Tooltip has to been created here, as the render callback only returns a string, not an object.
 					// Tooltip has to been created here, as the render callback only returns a string, not an object.
 					$(this).find('[data-toggle="tooltip"]').tooltip({container: 'body', html: true, trigger: 'hover', placement: 'right'});	// container option is necessary when in a table
 					$(this).find('[data-toggle="tooltip"]').tooltip({container: 'body', html: true, trigger: 'hover', placement: 'right'});	// container option is necessary when in a table
+					// Hiding pagination if only one page
+					if($(this).closest('.dataTables_wrapper').find('.dataTables_paginate:last .paginate_button:not(.previous):not(.next)').length < 2)
+					{
+						$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').hide();
+					}
+					else
+					{
+						$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show();
+					}
 				},
 				},
 				{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
 				{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
 					"data": oRawDatas,
 					"data": oRawDatas,

+ 9 - 7
datamodels/2.x/itop-portal-base/portal/src/views/bricks/layout.html.twig

@@ -12,13 +12,15 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block pMainHeader %}
 {% block pMainHeader %}
-<div class="col-sm-6 col-md-8" id="main-header-title">
-	<h2>{% block pMainHeaderTitle %}{% endblock %}</h2>
-</div>
-<div class="col-sm-6 col-md-4" id="main-header-actions">
-	{% block pMainHeaderActions %}
-	{% endblock %}
-</div>
+	<div class="col-xs-12">
+		<div id="main-header-title">
+			<h2>{% block pMainHeaderTitle %}{% endblock %}</h2>
+		</div>
+		<div id="main-header-actions">
+			{% block pMainHeaderActions %}
+			{% endblock %}
+		</div>
+	</div>
 {% endblock %}
 {% endblock %}
 
 
 {% block pMainContent %}
 {% block pMainContent %}

+ 17 - 10
datamodels/2.x/itop-portal-base/portal/src/views/bricks/manage/layout.html.twig

@@ -7,17 +7,13 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block pMainHeaderActions %}
 {% block pMainHeaderActions %}
-		<div class="row">
-			<div class="col-sm-12">
-				{% if aGroupingTabsValues|length > 1 %}
-				<div class="btn-group btn-group-justified btn-group-sm">
-					{% for aGroupingTab in aGroupingTabsValues %}
-						<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sGroupingTab': aGroupingTab.value}) }}" class="btn btn-default {% if sGroupingTab is defined and sGroupingTab == aGroupingTab.value %}active{% endif %}">{{ aGroupingTab.label|raw }}</a>
-					{% endfor %}
-				</div>
-				{% endif %}
-			</div>
+	{% if aGroupingTabsValues|length > 1 %}
+		<div class="btn-group btn-group-sm btn_group_explicit">
+			{% for aGroupingTab in aGroupingTabsValues %}
+				<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sGroupingTab': aGroupingTab.value}) }}" class="btn btn-default {% if sGroupingTab is defined and sGroupingTab == aGroupingTab.value %}active{% endif %}">{{ aGroupingTab.label|raw }}</a>
+			{% endfor %}
 		</div>
 		</div>
+	{% endif %}
 {% endblock %}
 {% endblock %}
 
 
 {% block pMainContentHolder%}
 {% block pMainContentHolder%}
@@ -169,6 +165,17 @@
 					"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"ri><"col-sm-6"p>>',
 					"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"ri><"col-sm-6"p>>',
 					"columns": getColumnsDefinition('{{ sAreaId }}'),
 					"columns": getColumnsDefinition('{{ sAreaId }}'),
 					"order": [[0, "desc"]],
 					"order": [[0, "desc"]],
+					"drawCallback": function(settings){
+						// Hiding pagination if only one page
+						if($(this).closest('.dataTables_wrapper').find('.dataTables_paginate:last .paginate_button:not(.previous):not(.next)').length < 2)
+						{
+							$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').hide();
+						}
+						else
+						{
+							$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show();
+						}
+					},
 					{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
 					{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
 						"data": rawData['{{ sAreaId }}'],
 						"data": rawData['{{ sAreaId }}'],
 					{% else %}
 					{% else %}

+ 13 - 2
datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_search_regular.html.twig

@@ -135,10 +135,21 @@
 					$(oRow).find('td:first-child input').prop('checked', true);
 					$(oRow).find('td:first-child input').prop('checked', true);
 				}
 				}
 			},
 			},
+			"drawCallback": function(settings){
+				// Hiding pagination if only one page
+				if($(this).closest('.dataTables_wrapper').find('.dataTables_paginate:last .paginate_button:not(.previous):not(.next)').length < 2)
+				{
+					$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').hide();
+				}
+				else
+				{
+					$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show();
+				}
+			},
 			"processing": true,
 			"processing": true,
 			"serverSide": true,
 			"serverSide": true,
 			"ajax": {
 			"ajax": {
-				"url": "{{ app.url_generator.generate('p_object_search_from_attribute', {'sTargetAttCode': sTargetAttCode, 'sHostObjectClass': sHostObjectClass, 'sHostObjectId': sHostObjectId})|raw }}",
+				"url": "{{ app.url_generator.generate('p_object_search_from_attribute', {'sTargetAttCode': sTargetAttCode, 'sHostObjectClass': sHostObjectClass, 'sHostObjectId': sHostObjectId, 'ar_token': sActionRulesToken})|raw }}",
 				"data": function(d){
 				"data": function(d){
 					d.aObjectIdsToIgnore = {{ aSource.aObjectIdsToIgnore|json_encode()|raw }};
 					d.aObjectIdsToIgnore = {{ aSource.aObjectIdsToIgnore|json_encode()|raw }};
 					d.iPageNumber = Math.floor(d.start/d.length) + 1;
 					d.iPageNumber = Math.floor(d.start/d.length) + 1;
@@ -165,7 +176,7 @@
 				var iItemId = aData[i].id;
 				var iItemId = aData[i].id;
 				if(!(iItemId in oSelectedItems))
 				if(!(iItemId in oSelectedItems))
 				{
 				{
-					oSelectedItems[iItemId] = aData[i].name;
+					oSelectedItems[iItemId] = $('<textarea />').html(aData[i].name).text();
 				}
 				}
 			}
 			}
 		});
 		});

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig

@@ -8,7 +8,7 @@
 {% block pFormButtons %}
 {% block pFormButtons %}
 	{% if tIsModal is defined and tIsModal == true %}
 	{% if tIsModal is defined and tIsModal == true %}
 		<div class="form_btn_regular">
 		<div class="form_btn_regular">
-			<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal">
+			<input class="btn btn-primary form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal">
 		</div>
 		</div>
 	{% endif %}
 	{% endif %}
 {% endblock %}
 {% endblock %}

+ 3 - 3
datamodels/2.x/itop-portal-base/portal/src/views/bricks/tile.html.twig

@@ -6,12 +6,12 @@
 		<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}"
 		<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}"
 			{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
 			{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
 			{% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
 			{% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
-			 class="tile vertical-center" id="brick-{{ brick.GetId }}" data-brick-id="{{ brick.GetId }}">
+			 class="tile{# vertical-center#}" id="brick-{{ brick.GetId }}" data-brick-id="{{ brick.GetId }}">
 		<div class="tile_decoration">
 		<div class="tile_decoration">
-			<span class="{{ brick.GetDecorationClassHome }}"></span>
+			<span class="icon {{ brick.GetDecorationClassHome }}"></span>
 			
 			
 		</div>
 		</div>
-			<div clss="tile_body">
+			<div class="tile_body">
 				<div class="tile_title">{{ brick.GetTitleHome|dict_s }}</div>
 				<div class="tile_title">{{ brick.GetTitleHome|dict_s }}</div>
 				{% if brick.HasDescription %}
 				{% if brick.HasDescription %}
 					<div class="tile_description">{{ brick.GetDescription|dict_s }}</div>
 					<div class="tile_description">{{ brick.GetDescription|dict_s }}</div>

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/src/views/bricks/user-profile/layout.html.twig

@@ -79,7 +79,7 @@
 								</div>
 								</div>
 							</form>
 							</form>
 						{% else %}
 						{% else %}
-							pas le droit
+							{{ 'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator'|dict_s }}
 						{% endif %}
 						{% endif %}
 					</div>
 					</div>
 				</div>
 				</div>

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/src/views/home/layout.html.twig

@@ -25,7 +25,7 @@
 	<div class="row">
 	<div class="row">
 		<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
 		<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
 			<section class="row tiles_wrapper">
 			<section class="row tiles_wrapper">
-				{% for brick in app['combodo.portal.instance.conf'].bricks %}
+				{% for brick in app['combodo.portal.instance.conf'].bricks_ordering.home %}
 					{% if brick.GetVisibleHome %}
 					{% if brick.GetVisibleHome %}
 						{% include '' ~ brick.GetTileTemplatePath with {brick: brick} %}
 						{% include '' ~ brick.GetTileTemplatePath with {brick: brick} %}
 					{% endif %}
 					{% endif %}

+ 2 - 2
datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig

@@ -124,7 +124,7 @@
 									{{ 'Page:Home'|dict_s }}
 									{{ 'Page:Home'|dict_s }}
 								</a>
 								</a>
 							</li>
 							</li>
-							{% for brick in app['combodo.portal.instance.conf'].bricks %}
+							{% for brick in app['combodo.portal.instance.conf'].bricks_ordering.navigation_menu %}
 								{% if brick.GetActive and brick.GetVisibleNavigationMenu and brick.GetRouteName is not null %}
 								{% if brick.GetActive and brick.GetVisibleNavigationMenu and brick.GetRouteName is not null %}
 									<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
 									<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
 										<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>
 										<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>
@@ -201,7 +201,7 @@
 								{{ 'Page:Home'|dict_s }}
 								{{ 'Page:Home'|dict_s }}
 							</a>
 							</a>
 						</li>
 						</li>
-						{% for brick in app['combodo.portal.instance.conf'].bricks %}
+						{% for brick in app['combodo.portal.instance.conf'].bricks_ordering.navigation_menu %}
 							{% if brick.GetActive and brick.GetVisibleNavigationMenu and brick.GetRouteName is not null %}
 							{% if brick.GetActive and brick.GetVisibleNavigationMenu and brick.GetRouteName is not null %}
 								<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
 								<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
 									<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>
 									<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>

+ 84 - 64
datamodels/2.x/itop-portal-base/portal/web/css/portal.css

@@ -1,6 +1,13 @@
 /*******************/
 /*******************/
 /* Global settings */
 /* Global settings */
 /*******************/
 /*******************/
+@font-face {
+    font-family: 'Combodo';
+    src: url('../fonts/fontawesome-webfont.woff') format('woff'), url('../fonts/fontawesome-webfont.ttf') format('truetype');
+    font-weight: normal;
+	font-style: normal;
+}
+
 @media (max-width: 768px){
 @media (max-width: 768px){
 	body{
 	body{
 		padding-top: 60px;
 		padding-top: 60px;
@@ -47,7 +54,7 @@ footer{
 	padding: 30px 0px;
 	padding: 30px 0px;
 	background-color: #EA7D1E; /* TODO : Change this */
 	background-color: #EA7D1E; /* TODO : Change this */
 	text-align: center;
 	text-align: center;
-	box-shadow: -1px 1px 4px rgba(0, 0, 0, 0.3)
+	box-shadow: -3px 2px 3px rgba(0, 0, 0, 0.4);
 }
 }
 #sidebar .user_card .user_photo{
 #sidebar .user_card .user_photo{
 	margin-bottom: 10px;
 	margin-bottom: 10px;
@@ -72,15 +79,13 @@ footer{
 	font-weight: 600;
 	font-weight: 600;
 }
 }
 #sidebar .menu{
 #sidebar .menu{
-	padding-top: 2em;
 	max-height: 59%;
 	max-height: 59%;
 	overflow-y: auto;
 	overflow-y: auto;
 }
 }
 #sidebar .menu .nav > li{
 #sidebar .menu .nav > li{
 	line-height: 3.0em;
 	line-height: 3.0em;
 }
 }
-#sidebar .menu .nav > li:after,
-#sidebar .menu .nav > li:first-child:before{
+#sidebar .menu .nav > li:after{
 	content: "";
 	content: "";
     display: block;
     display: block;
     position: relative;
     position: relative;
@@ -89,6 +94,9 @@ footer{
     color: #FFFFFF;
     color: #FFFFFF;
     border-bottom: 1px solid #7D7D7D;
     border-bottom: 1px solid #7D7D7D;
 }
 }
+#sidebar .menu .nav > li.active{
+	box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.8) inset;
+}
 #sidebar .menu .nav > li.active:after{
 #sidebar .menu .nav > li.active:after{
 	content: " ";
 	content: " ";
     position: absolute;
     position: absolute;
@@ -102,9 +110,6 @@ footer{
 	margin-top: -1px; /* To mask border from previous li item */
 	margin-top: -1px; /* To mask border from previous li item */
 	/*margin-left: 4em;
 	/*margin-left: 4em;
 	padding-left: 1em;*/
 	padding-left: 1em;*/
-	background-color: #EA7D1E;
-	color: #FFFFFF;
-	box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3)
 }
 }
 #sidebar .menu .nav > li > a{
 #sidebar .menu .nav > li > a{
 	padding-left: 3em;
 	padding-left: 3em;
@@ -125,6 +130,13 @@ footer{
 	max-width: 100%;
 	max-width: 100%;
 }
 }
 
 
+/* Main content */
+@media (min-width: 768px) {
+	#main-wrapper{
+		margin-top: 20px;
+	}
+}
+
 /* Overlays*/
 /* Overlays*/
 .global_overlay{
 .global_overlay{
 	z-index: 9999;
 	z-index: 9999;
@@ -167,31 +179,6 @@ footer{
 /******************/
 /******************/
 /* Global classes */
 /* Global classes */
 /******************/
 /******************/
-/*@media (min-width: 768px) {
-	.row-eq-height-sm {
-		display: -webkit-box;
-		display: -webkit-flex;
-		display: -ms-flexbox;
-		display: flex;
-	}
-}
-@media (min-width: 992px) {
-	.row-eq-height-md {
-		display: -webkit-box;
-		display: -webkit-flex;
-		display: -ms-flexbox;
-		display: flex;
-	}
-}
-@media (min-width: 1200px) {
-	.row-eq-height-lg {
-		display: -webkit-box;
-		display: -webkit-flex;
-		display: -ms-flexbox;
-		display: flex;
-	}
-}*/
-
 .vertical-center {
 .vertical-center {
 	/* Make it a flex container */
 	/* Make it a flex container */
 	display: -webkit-box;
 	display: -webkit-box;
@@ -293,9 +280,9 @@ a{
 	background-color: #FFFFFF;
 	background-color: #FFFFFF;
 	color: #EA7D1E;
 	color: #EA7D1E;
 }
 }
-.navbar-default .nav > li.active > a{
-	background-color: #FFFFFF;
-	color: #EA7D1E;
+.navbar-default .nav > li.active{
+	background-color: #292827; /* TODO Darken #585653 */
+	color: #FFFFFF;
 	font-weight: 600;
 	font-weight: 600;
 }
 }
 
 
@@ -343,15 +330,37 @@ a{
     border: 1px solid rgba(0, 0, 0, 0.15);
     border: 1px solid rgba(0, 0, 0, 0.15);
 }
 }
 .btn-primary:hover,
 .btn-primary:hover,
-.btn-primary.active:focus, {
-	background-color: #EA7D1E; /* TODO : Darken */
+.btn-primary:active:hover, .btn-primary.active:hover, 
+.btn-primary:active:focus, .btn-primary.active:focus, 
+.btn-primary:active.focus, .btn-primary.active.focus{
+	background-color: #DA751C; /* TODO : Darken #EA7D1E */
+}
+
+/* Button groups */
+.btn-group.btn_group_explicit{
+	padding: 7px;
+	background-color: #292827;
+	border: 1px solid #EBEAEA;
+	border-radius: 30px;
+	box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.4) inset;	
+}
+.btn-group.btn_group_explicit .btn{
+	color: #FFFFFF;
+	background-color: transparent;
+	border: none;
+}
+.btn-group.btn_group_explicit .btn.active{
+	color: #6B6965;
+	background-color: #EDECEC;
+	border-radius: 37px !important;
+	box-shadow: -1px 1px 2px rgba(0, 0, 0, 0.4);
 }
 }
 
 
 /* Panels */
 /* Panels */
 .panel{
 .panel{
 	border: none;
 	border: none;
 	border-radius: 0px;
 	border-radius: 0px;
-	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);;
+	box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
 }
 }
 
 
 /* Forms */
 /* Forms */
@@ -458,11 +467,12 @@ a{
 	min-height: 4em;
 	min-height: 4em;
 	background-color: #FFFFFF;
 	background-color: #FFFFFF;
     background-image: none;
     background-image: none;
-    border: 1px solid #8A8A8A;
-    border-radius: 0px;
+    border: none;
+	border-radius: 0px;
     text-align: center;
     text-align: center;
 	text-decoration: none;
 	text-decoration: none;
 	white-space: normal;
 	white-space: normal;
+	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
 }
 }
 .home .tile .tile_decoration{
 .home .tile .tile_decoration{
 	position: absolute;
 	position: absolute;
@@ -483,38 +493,41 @@ a{
 }
 }
 @media (min-width: 768px) {
 @media (min-width: 768px) {
 	.home .tile{
 	.home .tile{
+		display: block;
 		margin-bottom: 40px;
 		margin-bottom: 40px;
 		min-height: 10em;
 		min-height: 10em;
+		text-align: left;
 	}
 	}
 	.home .tile .tile_decoration{
 	.home .tile .tile_decoration{
-		position: absolute;
-		top: -30px;
-		left: 0px;
-		width: 100%;
+		position: relative;
+		top: initial;
+		left: initial;
+		width: 150px;
+		text-align: center;
 	}
 	}
 	.home .tile .tile_decoration > img{
 	.home .tile .tile_decoration > img{
 		width: 55px;
 		width: 55px;
 		max-height: 55px;
 		max-height: 55px;
 	}
 	}
+	.home .tile .tile_body{
+		text-align: left
+	}
 	.home .tile .tile_title{
 	.home .tile .tile_title{
 		font-size: 1.0em;
 		font-size: 1.0em;
 	}
 	}
 	.home .tile .tile_description{
 	.home .tile .tile_description{
 		display: block;
 		display: block;
-		margin: 15px 20px 0px 20px;
+		margin-top: 15px;
 		text-align: justify;
 		text-align: justify;
 	}
 	}
 }
 }
 @media (min-width: 992px) {
 @media (min-width: 992px) {
 	.home .tile{
 	.home .tile{
 		min-height: 15em;
 		min-height: 15em;
+		padding-right: 50px;
 	}
 	}
-	.home .tile .tile_decoration{
-		top: -35px;
-	}
-	.home .tile .tile_decoration > img{
-		width: 85px;
-		max-height: 85px;
+	.home .tile .tile_decoration > span.icon{
+		font-size: 5em;
 	}
 	}
 	.home .tile .tile_title{
 	.home .tile .tile_title{
 		font-size: 1.4em;
 		font-size: 1.4em;
@@ -525,29 +538,36 @@ a{
 /* Modules settings */
 /* Modules settings */
 /********************/
 /********************/
 
 
-#main-header-title{
+#main-header{
 	text-align: center;
 	text-align: center;
 }
 }
+#main-header-title{
+	margin-bottom: 15px;
+}
+
+#main-header-actions{
+	margin-bottom: 15px;
+}
+#main-header-actions .btn-group .btn{
+	padding: 0em 1.5em;
+	line-height: 2.8em;
+	font-size: 14px;
+}
 @media (min-width: 768px) {
 @media (min-width: 768px) {
+	#main-header:after{
+		clear: both;
+	}
 	#main-header-title{
 	#main-header-title{
+		float: left;
+		margin-bottom: 0px;
 		min-height: 6em;
 		min-height: 6em;
 		text-align: left;
 		text-align: left;
 	}
 	}
-}
-
-#main-header-actions > .row{
-	margin-top: 20px;
-}
-@media(max-width: 768px){
 	#main-header-actions{
 	#main-header-actions{
-		margin-bottom: 20px;
+		float: right;
+		margin-bottom: 0px;
 	}
 	}
 }
 }
-#main-header-actions .btn-group .btn{
-	line-height: 2.1em;
-	font-size: 14px;
-	border-radius: 0px;
-}
 
 
 .dataTables_wrapper{
 .dataTables_wrapper{
 	padding: 10px 10px;
 	padding: 10px 10px;

BIN
datamodels/2.x/itop-portal-base/portal/web/fonts/Combodo.otf


BIN
datamodels/2.x/itop-portal-base/portal/web/fonts/Combodo.ttf


BIN
datamodels/2.x/itop-portal-base/portal/web/fonts/Combodo.woff


+ 27 - 6
datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml

@@ -1106,7 +1106,7 @@
 		  <decoration_class>
 		  <decoration_class>
 			  <default>fa fa-pencil-square fa-2x</default>
 			  <default>fa fa-pencil-square fa-2x</default>
 		  </decoration_class>
 		  </decoration_class>
-          <oql><![CDATA[SELECT Ticket WHERE org_id = :current_contact->org_id AND caller_id = :current_contact_id]]></oql>
+          <oql><![CDATA[SELECT Ticket]]></oql>
           <!-- Can be either a class tag with the class name or an oql tag with the query -->
           <!-- Can be either a class tag with the class name or an oql tag with the query -->
           <!-- <class>Ticket</class> -->
           <!-- <class>Ticket</class> -->
           <fields>
           <fields>
@@ -1146,7 +1146,7 @@
         <brick id="closed-tickets-for-portal-user" xsi:type="Combodo\iTop\Portal\Brick\ManageBrick">
         <brick id="closed-tickets-for-portal-user" xsi:type="Combodo\iTop\Portal\Brick\ManageBrick">
           <active>true</active>
           <active>true</active>
           <rank>
           <rank>
-			  <menu>50</menu>
+			  <navigation_menu>50</navigation_menu>
 		  </rank>
 		  </rank>
           <visible>
           <visible>
             <home>false</home>
             <home>false</home>
@@ -1318,6 +1318,31 @@
             <mode id="edit"/>
             <mode id="edit"/>
           </modes>
           </modes>
         </form>
         </form>
+		<form id="service-view">
+			<class>Service</class>
+			<fields></fields>
+			<twig>
+			  <div class="row">
+				<div class="col-sm-6">
+				  <div class="form_field" data-field-id="name">
+				  </div>
+				  <div class="form_field" data-field-id="description">
+				  </div>
+				</div>
+				<div class="col-sm-6">
+				  <div class="form_field" data-field-id="org_id">
+				  </div>
+				  <div class="form_field" data-field-id="servicefamily_id">
+				  </div>
+				  <div class="form_field" data-field-id="status">
+				  </div>
+				</div>
+			  </div>
+			</twig>
+			<modes>
+				<mode id="view" />
+			</modes>
+		</form>
       </forms>
       </forms>
       <classes>
       <classes>
         <!-- Note : A class (or one of its ancestors) MUST be declared here to be displayed in the portal -->
         <!-- Note : A class (or one of its ancestors) MUST be declared here to be displayed in the portal -->
@@ -1352,10 +1377,6 @@
         </class>
         </class>
         <class id="Person">
         <class id="Person">
           <scopes>
           <scopes>
-            <scope id="all">
-              <oql_view><![CDATA[SELECT Person AS P WHERE P.org_id = :current_contact->org_id OR P.id = :current_contact_id]]></oql_view>
-              <oql_edit><![CDATA[SELECT Person AS P WHERE P.id = :current_contact_id]]></oql_edit>
-            </scope>
             <scope id="administrator">
             <scope id="administrator">
               <oql_view><![CDATA[SELECT Person AS P]]></oql_view>
               <oql_view><![CDATA[SELECT Person AS P]]></oql_view>
               <allowed_profiles>
               <allowed_profiles>

+ 7 - 0
sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php

@@ -184,6 +184,13 @@ EOF
 							me.element.find('#{$this->oField->GetGlobalId()}').val(sItemId);
 							me.element.find('#{$this->oField->GetGlobalId()}').val(sItemId);
 							me.element.find('#{$sAutocompleteFieldId}').val(sItemName);
 							me.element.find('#{$sAutocompleteFieldId}').val(sItemName);
 							oAutocompleteSource_{$this->oField->GetId()}.index.datums[sItemId] = {id: sItemId, name: sItemName};
 							oAutocompleteSource_{$this->oField->GetId()}.index.datums[sItemId] = {id: sItemId, name: sItemName};
+
+							// Triggering field change event
+							me.element.closest(".field_set").trigger("field_change", {
+								id: me.element.find('#{$this->oField->GetGlobalId()}').attr("id"),
+								name: me.element.find('#{$this->oField->GetGlobalId()}').attr("name"),
+								value: me.element.find('#{$this->oField->GetGlobalId()}').val()
+							});
 						}
 						}
 					});
 					});
 EOF
 EOF