N°2847 - Rework iTopWebPage layout (WIP Part VI)

- iTopWebPage: Restore "open search" feature
- iTopWebPage: Change all resources URL to absolute in order to benefit from the "duplicate removal" benefits
- iTopWebPage: Remove obsolete method IsMenuPaneVisible()
- Config: Add new parameters quick_create.enabled / global_search.enabled / breadcrumb.enabled
- utils: Add new GetAppRevisionNumber() method
- Introduce iUIBlock interface for UI layouts, components, ...
- Introduce BlockRenderer to properly render blocks
- Add "render_block" function to TwigHelper to render blocks directly from TWIG
- Refactor layouts and components into proper block classes to fit the new architecture
This commit is contained in:
Molkobain
2020-07-29 18:43:31 +02:00
parent b207ae1bb3
commit 1f0211b45a
43 changed files with 2537 additions and 335 deletions

View File

@@ -21,10 +21,10 @@ require_once(APPROOT."/application/nicewebpage.class.inc.php");
require_once(APPROOT."/application/applicationcontext.class.inc.php");
require_once(APPROOT."/application/user.preferences.class.inc.php");
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\QuickCreate\QuickCreateHelper;
use Combodo\iTop\Application\GlobalSearch\GlobalSearchHelper;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenuFactory;
use Combodo\iTop\Application\UI\Layout\TopBar\TopBarFactory;
use Combodo\iTop\Application\UI\UIBlock;
/**
* Web page with some associated CSS and scripts (jquery) for a fancier display
@@ -87,47 +87,48 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
// TODO: Should we keep this? Makes no sense
//$this->add_header("Cache-control: no-cache");
// TODO: Add only what's necessary
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
$this->add_linked_stylesheet("../css/magnific-popup.css");
$this->add_linked_stylesheet("../css/c3.min.css");
$this->add_linked_stylesheet("../node_modules/tippy.js/dist/tippy.css");
$this->add_linked_stylesheet("../node_modules/tippy.js/animations/shift-away-subtle.css");
$this->add_linked_stylesheet("../css/font-awesome/css/all.min.css");
$this->add_linked_stylesheet("../css/font-combodo/font-combodo.css");
$this->add_linked_stylesheet("../js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css");
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.treeview.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery-ui-timepicker-addon.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.multiselect.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/magnific-popup.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/c3.min.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/animations/shift-away-subtle.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-combodo/font-combodo.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css');
// TODO: Add only what's necessary
$this->add_linked_script('../js/jquery.layout.min.js');
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
$this->add_linked_script("../js/jquery.treeview.js");
$this->add_linked_script("../js/date.js");
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
$this->add_linked_script("../js/jquery.blockUI.js");
$this->add_linked_script("../js/utils.js");
$this->add_linked_script("../js/swfobject.js");
$this->add_linked_script("../js/ckeditor/ckeditor.js");
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
$this->add_linked_script("../js/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js");
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.layout.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.ba-bbq.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.treeview.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/date.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-timepicker-addon.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-timepicker-addon-i18n.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.blockUI.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/swfobject.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/ckeditor/ckeditor.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/ckeditor/adapters/jquery.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js');
/* @deprecated qTip will be removed in 2.9.0, use Tippy.js instead */
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_linked_script("../node_modules/@popperjs/core/dist/umd/popper.js");
$this->add_linked_script("../node_modules/tippy.js/dist/tippy-bundle.umd.js");
$this->add_linked_script('../js/property_field.js');
$this->add_linked_script('../js/icon_select.js');
$this->add_linked_script('../js/raphael-min.js');
$this->add_linked_script('../js/d3.js');
$this->add_linked_script('../js/c3.js');
$this->add_linked_script('../js/jquery.multiselect.js');
$this->add_linked_script('../js/ajaxfileupload.js');
$this->add_linked_script('../js/jquery.mousewheel.js');
$this->add_linked_script('../js/jquery.magnific-popup.min.js');
$this->add_linked_script('../js/moment-with-locales.min.js');
$this->add_linked_script('../js/showdown.min.js');
$this->add_linked_script('../js/pages/backoffice.js');
$this->add_linked_script('../js/newsroom_menu.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.qtip-1.0.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/property_field.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/icon_select.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/raphael-min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/d3.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/c3.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.multiselect.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/ajaxfileupload.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.mousewheel.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.magnific-popup.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/moment-with-locales.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/showdown.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/pages/backoffice.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/newsroom_menu.js');
$this->add_dict_entry('UI:FillAllMandatoryFields');
@@ -144,33 +145,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
}
}
/**
* Return true is the navigation menu should be expanded
*
* @return bool
*/
protected function IsMenuPaneVisible()
{
$bIsExpanded = false;
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Leave the menu collapsed
}
else
{
if (utils::ReadParam('force_menu_pane', null) === 0)
{
$bIsExpanded = false;
}
elseif (appUserPreferences::GetPref('menu_pane', 'closed') === 'opened')
{
$bIsExpanded = true;
}
}
return $bIsExpanded;
}
/**
*
*/
@@ -675,14 +649,16 @@ JS
}
/**
* @see static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_IMAGE, static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES
*
* @param string $sId Identifies the item, to search after it in the current breadcrumb
* @param string $sLabel Label of the breadcrumb item
* @param string $sDescription More information, displayed as a tooltip
* @param string $sUrl Specify a URL if the current URL as perceived on the browser side is not relevant
* @param string $sIcon Image URL (relative or absolute) or CSS classes (eg. "fas fa-wrench") of the icon that will be displayed next to the label
* @param string $sIconType Type of the icon, must be set according to the $sIcon value. See class constants ENUM_BREADCRUMB_ENTRY_ICON_TYPE_XXX
*
* @see static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_IMAGE, static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES
* @param string $sIcon Image URL (relative or absolute) or CSS classes (eg. "fas fa-wrench") of the icon that will be displayed next
* to the label
* @param string $sIconType Type of the icon, must be set according to the $sIcon value. See class constants
* ENUM_BREADCRUMB_ENTRY_ICON_TYPE_XXX
*/
public function SetBreadCrumbEntry($sId, $sLabel, $sDescription, $sUrl = '', $sIcon = '', $sIconType = self::DEFAULT_BREADCRUMB_ENTRY_ICON_TYPE)
{
@@ -812,209 +788,70 @@ JS
}
/**
* Return the complete revision number of the application
* Return the navigation menu layout (id, menu groups, ...)
*
* @return string
* @since 2.8.0
*/
protected function GetApplicationRevisionNumber()
{
if (ITOP_REVISION == 'svn')
{
// This is NOT a version built using the build system, just display the main version
$sRevisionNumber = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
}
else
{
// This is a build made from SVN, let display the full information
$sRevisionNumber = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
}
return $sRevisionNumber;
}
/**
* Return the navigation menu data (id, menu groups, ...)
*
* @return array
* @throws \Exception
* @internal
* @return \Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenu
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @since 2.8.0
* @internal
*/
protected function GetNavigationMenuData()
protected function GetNavigationMenuLayout()
{
$oAppContext = new ApplicationContext();
$aData = [
'sId' => 'ibo-navigation-menu',
'sAppRevisionNumber' => $this->GetApplicationRevisionNumber(),
'sAppSquareIconUrl' => Branding::GetSquareMainLogoAbsoluteUrl(),
'sAppFullIconUrl' => Branding::GetFullMainLogoAbsoluteUrl(),
'sAppIconLink' => MetaModel::GetConfig()->Get('app_icon_url'),
'aMenuGroups' => ApplicationMenu::GetMenuGroups($oAppContext->GetAsHash()),
'bIsExpanded' => $this->IsMenuPaneVisible(),
];
// TODO: Move this in the PHP component when designed
$this->add_linked_script('../js/layouts/navigation-menu.js');
// ... and this in a dedicated JS TWIG
$this->add_ready_script(<<<JS
$('#{$aData['sId']}').navigation_menu();
JS
);
return $aData;
return NavigationMenuFactory::MakeStandard();
}
/**
* Return the top bar data (global search, breadcrumbs, ...)
* Return the top bar layout (global search, breadcrumbs, ...)
*
* @return array
* @throws \ConfigException
* @internal
* @return \Combodo\iTop\Application\UI\Layout\TopBar\TopBar
* @throws \CoreException
* @throws \Exception
* @throws \CoreUnexpectedValue
* @since 2.8.0
* @internal
*/
protected function GetTopBarData()
protected function GetTopBarLayout()
{
$aData = [
'sId' => 'ibo-top-bar',
'aComponents' => [
'aQuickCreate' => $this->GetQuickCreateData(),
'aGlobalSearch' => $this->GetGlobalSearchData(),
'aBreadCrumbs' => $this->GetBreadCrumbsData(),
],
];
return $aData;
return TopBarFactory::MakeStandard($this->GetBreadCrumbsNewEntry());
}
/**
* Return the quick create data (last classes)
* Return the new breadcrumbs entry or null if we don't create a new entry for the current page
*
* @return array
* @throws \Exception
* @since 2.8.0
* @internal
*/
protected function GetQuickCreateData()
{
$aData = [
'sId' => 'ibo-quick-create',
'sEndpoint' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php',
'aAvailableClasses' => UserRights::GetAllowedClasses(UR_ACTION_CREATE, array('bizmodel'), true),
'aLastClasses' => QuickCreateHelper::GetLastClasses(),
];
// TODO: Move this in the PHP component when designed
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/components/quick-create.js');
// ... and this in a dedicated JS TWIG
$this->add_ready_script(<<<JS
$('#{$aData['sId']}').quick_create();
JS
);
return $aData;
}
/**
* Return the global search data (last queries)
*
* @return array
* @throws \Exception
* @return array|null
* @since 2.8.0
* @internal
*/
protected function GetGlobalSearchData()
protected function GetBreadCrumbsNewEntry()
{
$aData = [
'sId' => 'ibo-global-search',
'sEndpoint' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=full_text',
'aLastQueries' => GlobalSearchHelper::GetLastQueries(),
];
$aNewEntry = null;
// TODO: Move this in the PHP component when designed
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/components/global-search.js');
// ... and this in a dedicated JS TWIG
$this->add_ready_script(<<<JS
$('#{$aData['sId']}').global_search();
JS
);
return $aData;
}
/**
* Return the breadcrumbs data (iTop instance ID, new entry, ...)
*
* @return array
* @throws \ConfigException
* @throws \CoreException
* @since 2.8.0
* @internal
*/
protected function GetBreadCrumbsData()
{
$aData = [
'sId' => 'ibo-breadcrumbs',
'aWidgetOptions' => [],
];
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
if ($iBreadCrumbMaxCount > 1)
if ($this->bBreadCrumbEnabled)
{
$oConfig = MetaModel::GetConfig();
$siTopInstanceId = $oConfig->GetItopInstanceid();
if ($this->bBreadCrumbEnabled)
// Default entry values
if (is_null($this->sBreadCrumbEntryId))
{
// Default entry values
if (is_null($this->sBreadCrumbEntryId))
{
$this->sBreadCrumbEntryId = $this->s_title;
$this->sBreadCrumbEntryLabel = $this->s_title;
$this->sBreadCrumbEntryDescription = $this->s_title;
$this->sBreadCrumbEntryUrl = '';
$this->sBreadCrumbEntryIcon = 'fas fa-wrench';
$this->sBreadCrumbEntryIconType = static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES;
}
$aNewEntry = array(
'id' => $this->sBreadCrumbEntryId,
'url' => $this->sBreadCrumbEntryUrl,
'label' => utils::HtmlEntities($this->sBreadCrumbEntryLabel),
'description' => utils::HtmlEntities($this->sBreadCrumbEntryDescription),
'icon' => $this->sBreadCrumbEntryIcon,
'icon_type' => $this->sBreadCrumbEntryIconType,
);
}
else
{
$aNewEntry = null;
$this->sBreadCrumbEntryId = $this->s_title;
$this->sBreadCrumbEntryLabel = $this->s_title;
$this->sBreadCrumbEntryDescription = $this->s_title;
$this->sBreadCrumbEntryUrl = '';
$this->sBreadCrumbEntryIcon = 'fas fa-wrench';
$this->sBreadCrumbEntryIconType = static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES;
}
$aData['aWidgetOptions'] = [
'itop_instance_id' => $siTopInstanceId,
'max_count' => $iBreadCrumbMaxCount,
'new_entry' => $aNewEntry,
$aNewEntry = [
'id' => $this->sBreadCrumbEntryId,
'url' => $this->sBreadCrumbEntryUrl,
'label' => utils::HtmlEntities($this->sBreadCrumbEntryLabel),
'description' => utils::HtmlEntities($this->sBreadCrumbEntryDescription),
'icon' => $this->sBreadCrumbEntryIcon,
'icon_type' => $this->sBreadCrumbEntryIconType,
];
}
// TODO: Move this in the PHP component when designed
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/components/breadcrumbs.js');
// ... and this in a dedicated JS TWIG
$sWidgetOptionsAsJson = json_encode($aData['aWidgetOptions']);
$this->add_ready_script(<<<JS
// Note: When refactor in the JS TWIG, use {{ aBreadCrumbs.aWidgetOptions|json_encode|raw }}
$('#{$aData['sId']}').breadcrumbs($sWidgetOptionsAsJson);
JS
);
return $aData;
return $aNewEntry;
}
/**
@@ -1194,12 +1031,34 @@ EOF
'sHeader' => $this->RenderHeaderHtml(),
'sFooter' => $this->RenderFooterHtml(),
];
// - Prepare navigation menu
$aData['aLayouts']['oNavigationMenu'] = $this->GetNavigationMenuLayout();
// - Prepare top bar
$aData['aLayouts']['oTopBar'] = $this->GetTopBarLayout();
// - Retrieve layouts linked files
// Note: Adding them now instead of in the template allow us to remove duplicates and lower the browser parsing time
/** @var \Combodo\iTop\Application\UI\UIBlock|string $oLayout */
foreach ($aData['aLayouts'] as $oLayout)
{
if (!$oLayout instanceof UIBlock)
{
continue;
}
// - Navigation menu
$aData['aLayouts']['aNavigationMenu'] = $this->GetNavigationMenuData();
// CSS files
foreach ($oLayout->GetCssFilesUrlRecursively(true) as $sFileAbsUrl)
{
$this->add_linked_stylesheet($sFileAbsUrl);
}
// JS files
foreach ($oLayout->GetJsFilesUrlRecursively(true) as $sFileAbsUrl)
{
$this->add_linked_script($sFileAbsUrl);
}
}
// - Top bar
$aData['aLayouts']['aTopBar'] = $this->GetTopBarData();
// Components
// Note: For now all components are either included in the layouts above or put in page through the add_ui_block() API, so there is no need to do anything more.
// Variable content of the page
$aData['aPage'] = array_merge(
@@ -1292,10 +1151,10 @@ EOF
{
$sHtml .= $this->output_dict_entries(true); // before any script so that they can benefit from the translations
if (!$this->IsPrintableVersion())
{
$this->add_script("var iPaneVisWatchDog = window.setTimeout('FixPaneVis()',5000);");
}
// if (!$this->IsPrintableVersion())
// {
// $this->add_script("var iPaneVisWatchDog = window.setTimeout('FixPaneVis()',5000);");
// }
// TODO: Should we still do this init vs ready separation?
@@ -1333,8 +1192,7 @@ EOF
}
// TODO: Does this still work?
$sHtml .= "<link rel=\"search\" type=\"application/opensearchdescription+xml\" title=\"iTop\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/opensearch.xml.php\" />\n";
// $sHtml .= "<link rel=\"search\" type=\"application/opensearchdescription+xml\" title=\"iTop\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/opensearch.xml.php\" />\n";
$sHtml .= "</head>\n";
@@ -1379,10 +1237,10 @@ EOF;
$sHtml .= "<div class=\"printable-content\" style=\"width: $sDefaultResolution;\">";
}
// Render the text of the global search form
$sText = htmlentities(utils::ReadParam('text', '', false, 'raw_data'), ENT_QUOTES, self::PAGES_CHARSET);
$sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
$sDefaultPlaceHolder = Dict::S("UI:YourSearch");
// // Render the text of the global search form
// $sText = htmlentities(utils::ReadParam('text', '', false, 'raw_data'), ENT_QUOTES, self::PAGES_CHARSET);
// $sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
// $sDefaultPlaceHolder = Dict::S("UI:YourSearch");
if ($this->IsPrintableVersion())
{
@@ -1409,57 +1267,57 @@ EOF;
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
$aActions = array();
$aAllowedPortals = UserRights::GetAllowedPortals();
if (count($aAllowedPortals) > 1)
{
// Adding portals
foreach ($aAllowedPortals as $aAllowedPortal)
{
if ($aAllowedPortal['id'] !== 'backoffice')
{
$oPortalMenuItem = new URLPopupMenuItem('portal:'.$aAllowedPortal['id'], Dict::S($aAllowedPortal['label']),
$aAllowedPortal['url'], '_blank');
$aActions[$oPortalMenuItem->GetUID()] = $oPortalMenuItem->GetMenuItem();
}
}
// Adding a separator
$oPortalSeparatorMenuItem = new SeparatorPopupMenuItem();
$aActions[$oPortalSeparatorMenuItem->GetUID()] = $oPortalSeparatorMenuItem->GetMenuItem();
}
// $aAllowedPortals = UserRights::GetAllowedPortals();
// if (count($aAllowedPortals) > 1)
// {
// // Adding portals
// foreach ($aAllowedPortals as $aAllowedPortal)
// {
// if ($aAllowedPortal['id'] !== 'backoffice')
// {
// $oPortalMenuItem = new URLPopupMenuItem('portal:'.$aAllowedPortal['id'], Dict::S($aAllowedPortal['label']),
// $aAllowedPortal['url'], '_blank');
// $aActions[$oPortalMenuItem->GetUID()] = $oPortalMenuItem->GetMenuItem();
// }
// }
// // Adding a separator
// $oPortalSeparatorMenuItem = new SeparatorPopupMenuItem();
// $aActions[$oPortalSeparatorMenuItem->GetUID()] = $oPortalSeparatorMenuItem->GetMenuItem();
// }
$oPrefs = new URLPopupMenuItem('UI:Preferences', Dict::S('UI:Preferences'),
utils::GetAbsoluteUrlAppRoot()."pages/preferences.php?".$oAppContext->GetForLink());
$aActions[$oPrefs->GetUID()] = $oPrefs->GetMenuItem();
// $oPrefs = new URLPopupMenuItem('UI:Preferences', Dict::S('UI:Preferences'),
// utils::GetAbsoluteUrlAppRoot()."pages/preferences.php?".$oAppContext->GetForLink());
// $aActions[$oPrefs->GetUID()] = $oPrefs->GetMenuItem();
if (utils::IsArchiveMode())
{
$oExitArchive = new JSPopupMenuItem('UI:ArchiveModeOff', Dict::S('UI:ArchiveModeOff'), 'return ArchiveMode(false);');
$aActions[$oExitArchive->GetUID()] = $oExitArchive->GetMenuItem();
// $oExitArchive = new JSPopupMenuItem('UI:ArchiveModeOff', Dict::S('UI:ArchiveModeOff'), 'return ArchiveMode(false);');
// $aActions[$oExitArchive->GetUID()] = $oExitArchive->GetMenuItem();
$sIcon = '<span class="fas fa-lock fa-1x"></span>';
$this->AddApplicationMessage(Dict::S('UI:ArchiveMode:Banner'), $sIcon, Dict::S('UI:ArchiveMode:Banner+'));
}
elseif (UserRights::CanBrowseArchive())
{
$oBrowseArchive = new JSPopupMenuItem('UI:ArchiveModeOn', Dict::S('UI:ArchiveModeOn'), 'return ArchiveMode(true);');
$aActions[$oBrowseArchive->GetUID()] = $oBrowseArchive->GetMenuItem();
}
if (utils::CanLogOff())
{
$oLogOff = new URLPopupMenuItem('UI:LogOffMenu', Dict::S('UI:LogOffMenu'),
utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff');
$aActions[$oLogOff->GetUID()] = $oLogOff->GetMenuItem();
}
if (UserRights::CanChangePassword())
{
$oChangePwd = new URLPopupMenuItem('UI:ChangePwdMenu', Dict::S('UI:ChangePwdMenu'),
utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=change_pwd');
$aActions[$oChangePwd->GetUID()] = $oChangePwd->GetMenuItem();
}
// elseif (UserRights::CanBrowseArchive())
// {
// $oBrowseArchive = new JSPopupMenuItem('UI:ArchiveModeOn', Dict::S('UI:ArchiveModeOn'), 'return ArchiveMode(true);');
// $aActions[$oBrowseArchive->GetUID()] = $oBrowseArchive->GetMenuItem();
// }
// if (utils::CanLogOff())
// {
// $oLogOff = new URLPopupMenuItem('UI:LogOffMenu', Dict::S('UI:LogOffMenu'),
// utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff');
// $aActions[$oLogOff->GetUID()] = $oLogOff->GetMenuItem();
// }
// if (UserRights::CanChangePassword())
// {
// $oChangePwd = new URLPopupMenuItem('UI:ChangePwdMenu', Dict::S('UI:ChangePwdMenu'),
// utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=change_pwd');
// $aActions[$oChangePwd->GetUID()] = $oChangePwd->GetMenuItem();
// }
utils::GetPopupMenuItems($this, iPopupMenuExtension::MENU_USER_ACTIONS, null, $aActions);
$oAbout = new JSPopupMenuItem('UI:AboutBox', Dict::S('UI:AboutBox'), 'return ShowAboutBox();');
$aActions[$oAbout->GetUID()] = $oAbout->GetMenuItem();
// $oAbout = new JSPopupMenuItem('UI:AboutBox', Dict::S('UI:AboutBox'), 'return ShowAboutBox();');
// $aActions[$oAbout->GetUID()] = $oAbout->GetMenuItem();
$sLogOffMenu .= $this->RenderPopupMenuItems($aActions);
// TODO: END USER MENU
@@ -1519,7 +1377,7 @@ EOF;
}
// TODO: What do we do with this?
$sOnlineHelpUrl = MetaModel::GetConfig()->Get('online_help');
// $sOnlineHelpUrl = MetaModel::GetConfig()->Get('online_help');
//$sLogOffMenu = "<span id=\"logOffBtn\" style=\"height:55px;padding:0;margin:0;\"><img src=\"../images/onOffBtn.png\"></span>";
// $sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/itop-logo.png?t='.utils::GetCacheBusterTimestamp();
@@ -1583,7 +1441,7 @@ EOF;
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php">';
$sHtml .= ' <table id="top-left-buttons-area"><tr>';
$sHtml .= ' <td id="top-left-global-search-cell"><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sDefaultPlaceHolder.'" value="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"><i class="top-right-icon fa-flip-horizontal fas fa-search"></i><input type="hidden" name="operation" value="full_text"/></div></div></td>';
$sHtml .= ' <td id="top-left-help-cell"><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank" title="'.Dict::S('UI:Help').'"><i class="top-right-icon fas fa-question-circle"></i></a></td>';
// $sHtml .= ' <td id="top-left-help-cell"><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank" title="'.Dict::S('UI:Help').'"><i class="top-right-icon fas fa-question-circle"></i></a></td>';
$sHtml .= ' <td id="top-left-newsroom-cell">'.$sNewsRoomInitialImage.'</td>';
$sHtml .= ' <td id="top-left-logoff-cell">'.self::FilterXSS($sLogOffMenu).'</td>';
$sHtml .= ' </tr></table></form></div>';

View File

@@ -909,6 +909,28 @@ class utils
return $sAppRootUrl;
}
/**
* Return the complete revision number of the application
*
* @return string
* @since 2.8.0
*/
public static function GetAppRevisionNumber()
{
if (ITOP_REVISION == 'svn')
{
// This is NOT a version built using the build system, just display the main version
$sRevisionNumber = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
}
else
{
// This is a build made from SVN, let display the full information
$sRevisionNumber = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
}
return $sRevisionNumber;
}
/**
* Helper to handle the variety of HTTP servers
* See N°286 (fixed in [896]), and N°634 (this fix)

View File

@@ -1137,6 +1137,30 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'quick_create.enabled' => array(
'type' => 'bool',
'description' => 'Whether or not the global search is enabled',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'global_search.enabled' => array(
'type' => 'bool',
'description' => 'Whether or not the global search is enabled',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'breadcrumb.enabled' => array(
'type' => 'bool',
'description' => 'Whether or not the breadcrumbs is enabled',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'breadcrumb.max_count' => array(
'type' => 'integer',
'description' => 'Maximum number of items kept in the history breadcrumb. Set it to 0 to entirely disable the breadcrumb.',

View File

@@ -102,6 +102,7 @@ $ibo-quick-create--compartment--placeholder-hint--text-color: $ibo-color-grey-70
}
.ibo-quick-create--input{
width: $ibo-quick-create--input--width;
border: none;
transition: all 0.2s ease-in-out;
/* Remove selectize.js theme and apply our own */

View File

@@ -138,8 +138,6 @@ return array(
'CheckStopWatchThresholds' => $baseDir . '/core/ormstopwatch.class.inc.php',
'CheckableExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
'Combodo\\iTop\\Application\\Branding' => $baseDir . '/sources/application/Branding.php',
'Combodo\\iTop\\Application\\GlobalSearch\\GlobalSearchHelper' => $baseDir . '/sources/application/GlobalSearch/GlobalSearchHelper.php',
'Combodo\\iTop\\Application\\QuickCreate\\QuickCreateHelper' => $baseDir . '/sources/application/QuickCreate/QuickCreateHelper.php',
'Combodo\\iTop\\Application\\Search\\AjaxSearchException' => $baseDir . '/sources/application/search/ajaxsearchexception.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversionAbstract' => $baseDir . '/sources/application/search/criterionconversionabstract.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversion\\CriterionToOQL' => $baseDir . '/sources/application/search/criterionconversion/criteriontooql.class.inc.php',
@@ -150,6 +148,25 @@ return array(
'Combodo\\iTop\\Application\\TwigBase\\Controller\\PageNotFoundException' => $baseDir . '/sources/application/TwigBase/Controller/Controller.php',
'Combodo\\iTop\\Application\\TwigBase\\Twig\\Extension' => $baseDir . '/sources/application/TwigBase/Twig/Extension.php',
'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => $baseDir . '/sources/application/TwigBase/Twig/TwigHelper.php',
'Combodo\\iTop\\Application\\UI\\Component\\Breadcrumbs\\Breadcrumbs' => $baseDir . '/sources/application/UI/Component/Breadcrumbs/Breadcrumbs.php',
'Combodo\\iTop\\Application\\UI\\Component\\GlobalSearch\\GlobalSearch' => $baseDir . '/sources/application/UI/Component/GlobalSearch/GlobalSearch.php',
'Combodo\\iTop\\Application\\UI\\Component\\GlobalSearch\\GlobalSearchFactory' => $baseDir . '/sources/application/UI/Component/GlobalSearch/GlobalSearchFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\GlobalSearch\\GlobalSearchHelper' => $baseDir . '/sources/application/UI/Component/GlobalSearch/GlobalSearchHelper.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenu' => $baseDir . '/sources/application/UI/Component/PopoverMenu/PopoverMenu.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuFactory' => $baseDir . '/sources/application/UI/Component/PopoverMenu/PopoverMenuFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\JsPopoverMenuItem' => $baseDir . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/JsPopoverMenuItem.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\PopoverMenuItem' => $baseDir . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItem.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\PopupMenuItemFactory' => $baseDir . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/PopupMenuItemFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\UrlPopoverMenuItem' => $baseDir . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/UrlPopoverMenuItem.php',
'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreate' => $baseDir . '/sources/application/UI/Component/QuickCreate/QuickCreate.php',
'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreateFactory' => $baseDir . '/sources/application/UI/Component/QuickCreate/QuickCreateFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreateHelper' => $baseDir . '/sources/application/UI/Component/QuickCreate/QuickCreateHelper.php',
'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenu' => $baseDir . '/sources/application/UI/Layout/NavigationMenu/NavigationMenu.php',
'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenuFactory' => $baseDir . '/sources/application/UI/Layout/NavigationMenu/NavigationMenuFactory.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TopBar\\TopBar' => $baseDir . '/sources/application/UI/Layout/TopBar/TopBar.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TopBar\\TopBarFactory' => $baseDir . '/sources/application/UI/Layout/TopBar/TopBarFactory.php',
'Combodo\\iTop\\Application\\UI\\UIBlock' => $baseDir . '/sources/application/UI/UIBlock.php',
'Combodo\\iTop\\Application\\UI\\iUIBlock' => $baseDir . '/sources/application/UI/iUIBlock.php',
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
@@ -186,6 +203,7 @@ return array(
'Combodo\\iTop\\Form\\Validator\\MandatoryValidator' => $baseDir . '/sources/Form/Validator/MandatoryValidator.php',
'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => $baseDir . '/sources/Form/Validator/NotEmptyExtKeyValidator.php',
'Combodo\\iTop\\Form\\Validator\\Validator' => $baseDir . '/sources/Form/Validator/Validator.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer' => $baseDir . '/sources/Renderer/Bootstrap/BsFormRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\FieldRenderer\\BsFieldRenderer' => $baseDir . '/sources/Renderer/Bootstrap/FieldRenderer/BsFieldRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\FieldRenderer\\BsFileUploadFieldRenderer' => $baseDir . '/sources/Renderer/Bootstrap/FieldRenderer/BsFileUploadFieldRenderer.php',

View File

@@ -368,8 +368,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'CheckStopWatchThresholds' => __DIR__ . '/../..' . '/core/ormstopwatch.class.inc.php',
'CheckableExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
'Combodo\\iTop\\Application\\Branding' => __DIR__ . '/../..' . '/sources/application/Branding.php',
'Combodo\\iTop\\Application\\GlobalSearch\\GlobalSearchHelper' => __DIR__ . '/../..' . '/sources/application/GlobalSearch/GlobalSearchHelper.php',
'Combodo\\iTop\\Application\\QuickCreate\\QuickCreateHelper' => __DIR__ . '/../..' . '/sources/application/QuickCreate/QuickCreateHelper.php',
'Combodo\\iTop\\Application\\Search\\AjaxSearchException' => __DIR__ . '/../..' . '/sources/application/search/ajaxsearchexception.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversionAbstract' => __DIR__ . '/../..' . '/sources/application/search/criterionconversionabstract.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversion\\CriterionToOQL' => __DIR__ . '/../..' . '/sources/application/search/criterionconversion/criteriontooql.class.inc.php',
@@ -380,6 +378,25 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Combodo\\iTop\\Application\\TwigBase\\Controller\\PageNotFoundException' => __DIR__ . '/../..' . '/sources/application/TwigBase/Controller/Controller.php',
'Combodo\\iTop\\Application\\TwigBase\\Twig\\Extension' => __DIR__ . '/../..' . '/sources/application/TwigBase/Twig/Extension.php',
'Combodo\\iTop\\Application\\TwigBase\\Twig\\TwigHelper' => __DIR__ . '/../..' . '/sources/application/TwigBase/Twig/TwigHelper.php',
'Combodo\\iTop\\Application\\UI\\Component\\Breadcrumbs\\Breadcrumbs' => __DIR__ . '/../..' . '/sources/application/UI/Component/Breadcrumbs/Breadcrumbs.php',
'Combodo\\iTop\\Application\\UI\\Component\\GlobalSearch\\GlobalSearch' => __DIR__ . '/../..' . '/sources/application/UI/Component/GlobalSearch/GlobalSearch.php',
'Combodo\\iTop\\Application\\UI\\Component\\GlobalSearch\\GlobalSearchFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/GlobalSearch/GlobalSearchFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\GlobalSearch\\GlobalSearchHelper' => __DIR__ . '/../..' . '/sources/application/UI/Component/GlobalSearch/GlobalSearchHelper.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenu' => __DIR__ . '/../..' . '/sources/application/UI/Component/PopoverMenu/PopoverMenu.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/PopoverMenu/PopoverMenuFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\JsPopoverMenuItem' => __DIR__ . '/../..' . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/JsPopoverMenuItem.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\PopoverMenuItem' => __DIR__ . '/../..' . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItem.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\PopupMenuItemFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/PopupMenuItemFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\UrlPopoverMenuItem' => __DIR__ . '/../..' . '/sources/application/UI/Component/PopoverMenu/PopoverMenuItem/UrlPopoverMenuItem.php',
'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreate' => __DIR__ . '/../..' . '/sources/application/UI/Component/QuickCreate/QuickCreate.php',
'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreateFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/QuickCreate/QuickCreateFactory.php',
'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreateHelper' => __DIR__ . '/../..' . '/sources/application/UI/Component/QuickCreate/QuickCreateHelper.php',
'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenu' => __DIR__ . '/../..' . '/sources/application/UI/Layout/NavigationMenu/NavigationMenu.php',
'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenuFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/NavigationMenu/NavigationMenuFactory.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TopBar\\TopBar' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TopBar/TopBar.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TopBar\\TopBarFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TopBar/TopBarFactory.php',
'Combodo\\iTop\\Application\\UI\\UIBlock' => __DIR__ . '/../..' . '/sources/application/UI/UIBlock.php',
'Combodo\\iTop\\Application\\UI\\iUIBlock' => __DIR__ . '/../..' . '/sources/application/UI/iUIBlock.php',
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
@@ -416,6 +433,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Combodo\\iTop\\Form\\Validator\\MandatoryValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/MandatoryValidator.php',
'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/NotEmptyExtKeyValidator.php',
'Combodo\\iTop\\Form\\Validator\\Validator' => __DIR__ . '/../..' . '/sources/Form/Validator/Validator.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFormRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\FieldRenderer\\BsFieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/FieldRenderer/BsFieldRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\FieldRenderer\\BsFileUploadFieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/FieldRenderer/BsFileUploadFieldRenderer.php',

View File

@@ -17,8 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\GlobalSearch\GlobalSearchHelper;
use Combodo\iTop\Application\QuickCreate\QuickCreateHelper;
use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearchHelper;
use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreateHelper;
/**
* Displays a popup welcome message, once per session at maximum

View File

@@ -0,0 +1,259 @@
<?php
/**
* Copyright (C) 2013-2020 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\Renderer;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\iUIBlock;
use ReflectionClass;
/**
* Class BlockRenderer
*
* Used to render any block of the UI (layouts, components)
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Renderer\Component
* @since 2.8.0
*/
class BlockRenderer
{
/** @var \Twig_Environment $oTwigEnv Singleton used during rendering */
protected static $oTwigEnv;
/**
* Helper to use directly in TWIG to render a block and its sub blocks
*
* @param \Combodo\iTop\Application\UI\iUIBlock $oBlock
*
* @return string
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public static function RenderBlockTemplates(iUIBlock $oBlock)
{
$oSelf = new static($oBlock);
return $oSelf->RenderTemplates();
}
/** @var \Combodo\iTop\Application\UI\iUIBlock $oBlock */
protected $oBlock;
/** @var \Combodo\iTop\Renderer\RenderingOutput $oRenderingOutput */
protected $oRenderingOutput;
/**
* BlockRenderer constructor.
*
* @param \Combodo\iTop\Application\UI\iUIBlock $oBlock
*/
public function __construct(iUIBlock $oBlock)
{
if(null === static::$oTwigEnv)
{
static::$oTwigEnv = TwigHelper::GetTwigEnvironment(APPROOT.'templates/');
}
$this->oBlock = $oBlock;
$this->ResetRenderingOutput();
}
/**
* Reset the rendering output so it can be computed again
*
* @return $this
*/
protected function ResetRenderingOutput()
{
$this->oRenderingOutput = new RenderingOutput();
return $this;
}
/**
* Return the processed rendering output.
*
* @return \Combodo\iTop\Renderer\RenderingOutput
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \Exception
*/
public function GetRenderingOutput()
{
$this->ResetRenderingOutput();
$this->oRenderingOutput->AddHtml($this->RenderHtml())
->AddCss($this->RenderCssInline())
->AddJs($this->RenderJsInline())
->SetCssFiles($this->GetCssFiles())
->SetJsFiles($this->GetJsFiles());
return $this->oRenderingOutput;
}
/**
* Return the raw output of the HTML template
*
* @return string
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function RenderHtml()
{
$sOutput = '';
if(!empty($this->oBlock::GetHtmlTemplateRelPath()))
{
$sOutput = TwigHelper::RenderTemplate(
static::$oTwigEnv,
[$this->GetBlockParameterNameForTemplate() => $this->oBlock],
$this->oBlock::GetHtmlTemplateRelPath(),
TwigHelper::ENUM_FILE_TYPE_HTML
);
}
return $sOutput;
}
/**
* Return the raw output of the JS template
*
* @return string
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function RenderJsInline()
{
$sOutput = '';
if(!empty($this->oBlock::GetJsTemplateRelPath()))
{
$sOutput = TwigHelper::RenderTemplate(
static::$oTwigEnv,
[$this->GetBlockParameterNameForTemplate() => $this->oBlock],
$this->oBlock::GetJsTemplateRelPath(),
TwigHelper::ENUM_FILE_TYPE_JS
);
}
return $sOutput;
}
/**
* Return the raw output of the CSS template
*
* @return string
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function RenderCssInline()
{
$sOutput = '';
if(!empty($this->oBlock::GetCssTemplateRelPath()))
{
$sOutput = TwigHelper::RenderTemplate(
static::$oTwigEnv,
[$this->GetBlockParameterNameForTemplate() => $this->oBlock],
$this->oBlock::GetCssTemplateRelPath(),
TwigHelper::ENUM_FILE_TYPE_CSS
);
}
return $sOutput;
}
/**
* Return the cumulated HTML output of the CSS, HTML and JS templates
*
* @return string
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function RenderTemplates()
{
$sOutput = '';
// CSS first to avoid visual glitches
$sCssOutput = $this->RenderCssInline();
if(!empty($sCssOutput))
{
$sOutput .= <<<HTML
<style>
{$sCssOutput}
</style>
HTML;
}
$sOutput .= $this->RenderHtml();
// JS last so all markup is build and ready
$sJsOutput = $this->RenderJsInline();
if(!empty($sJsOutput))
{
$sOutput .= <<<HTML
<script type="text/javascript">
{$sJsOutput}
</script>
HTML;
}
return $sOutput;
}
/**
* Return an array of the absolute URL of the block JS files
*
* @return array
* @throws \Exception
*/
public function GetJsFiles()
{
return $this->oBlock->GetJsFilesUrlRecursively(true);
}
/**
* Return an array of the absolute URL of the block CSS files
*
* @return array
* @throws \Exception
*/
public function GetCssFiles()
{
return $this->oBlock->GetCssFilesUrlRecursively(true);
}
/**
* Return the name of the parameter used in the template to access the block object (class short name = without namespace)
*
* @return string
* @throws \ReflectionException
*/
protected function GetBlockParameterNameForTemplate()
{
return 'o'.(new ReflectionClass($this->oBlock))->getShortName();
}
}

View File

@@ -165,6 +165,20 @@ class RenderingOutput
return $this;
}
/**
* Set the JS files (absolute URLs) and replace any existing ones.
*
* @param array $aFiles Array of absolute URLs
*
* @return $this
* @since 2.8.0
*/
public function SetJsFiles($aFiles)
{
$this->aJsFiles = $aFiles;
return $this;
}
/**
*
* @param string $sFile
@@ -204,6 +218,20 @@ class RenderingOutput
return $this;
}
/**
* Set the CSS files (absolute URLs) and replace any existing ones.
*
* @param array $aFiles Array of absolute URLs
*
* @return $this
* @since 2.8.0
*/
public function SetCssFiles($aFiles)
{
$this->aCssFiles = $aFiles;
return $this;
}
/**
*
* @param string $sFile

View File

@@ -8,6 +8,8 @@ namespace Combodo\iTop\Application\TwigBase\Twig;
use AttributeDateTime;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Renderer\BlockRenderer;
use Dict;
use Exception;
use MetaModel;
@@ -144,6 +146,13 @@ class Extension
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_absolute_url_modules_root', function () {
return utils::GetAbsoluteUrlModulesRoot();
}));
// Function to render a UI block (HTML, inline CSS, inline JS) and its sub blocks directly in the TWIG
// Usage in twig: {{ render_block(oBlock) }}
/** @since 2.8.0 */
$oTwigEnv->addFunction(new Twig_SimpleFunction('render_block', function(iUIBlock $oBlock){
return BlockRenderer::RenderBlockTemplates($oBlock);
}, ['is_safe' => ['html']]));
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\Breadcrumbs;
use Combodo\iTop\Application\UI\UIBlock;
use MetaModel;
use utils;
/**
* Class Breadcrumbs
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\Breadcrumbs
* @internal
* @since 2.8.0
*/
class Breadcrumbs extends UIBlock
{
const BLOCK_CODE = 'ibo-breadcrumbs';
const HTML_TEMPLATE_REL_PATH = 'components/breadcrumbs/layout';
const JS_TEMPLATE_REL_PATH = 'components/breadcrumbs/layout';
const JS_FILES_REL_PATH = [
'js/components/breadcrumbs.js',
];
/** @var array|null $aNewEntry */
protected $aNewEntry;
/**
* QuickCreate constructor.
*
* @param string $sId
* @param array|null $aNewEntry
*/
public function __construct($sId = null, $aNewEntry = null)
{
parent::__construct($sId);
$this->SetNewEntry($aNewEntry);
}
/**
* The new breadcrumbs entry
*
* @param array|null $aNewEntry
*
* @return $this
*/
public function SetNewEntry($aNewEntry)
{
$this->aNewEntry = $aNewEntry;
return $this;
}
/**
* @return array|null
*/
public function GetNewEntry()
{
return $this->aNewEntry;
}
/**
* @return array
* @throws \ConfigException
* @throws \CoreException
* @throws \Exception
*/
public function GetJsWidgetOptions()
{
$aJsWidgetOptions = [];
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
if ($iBreadCrumbMaxCount > 1)
{
$oConfig = MetaModel::GetConfig();
$siTopInstanceId = $oConfig->GetItopInstanceid();
$aJsWidgetOptions = [
'itop_instance_id' => $siTopInstanceId,
'max_count' => $iBreadCrumbMaxCount,
'new_entry' => $this->GetNewEntry(),
];
}
return $aJsWidgetOptions;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\GlobalSearch;
use Combodo\iTop\Application\UI\UIBlock;
use utils;
/**
* Class GlobalSearch
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\GlobalSearch
* @internal
* @since 2.8.0
*/
class GlobalSearch extends UIBlock
{
const BLOCK_CODE = 'ibo-global-search';
const HTML_TEMPLATE_REL_PATH = 'components/global-search/layout';
const JS_TEMPLATE_REL_PATH = 'components/global-search/layout';
const JS_FILES_REL_PATH = [
'js/components/global-search.js',
];
const DEFAULT_ENDPOINT_REL_URL = 'pages/UI.php?operation=full_text';
/** @var string $sEndpoint Absolute endpoint URL of the search form */
protected $sEndpoint;
/** @var array $aLastQueries */
protected $aLastQueries;
/**
* GlobalSearch constructor.
*
* @param string $sId
* @param array $aLastClasses
*
* @throws \Exception
*/
public function __construct($sId = null, $aLastClasses = [])
{
parent::__construct($sId);
$this->SetEndpoint(static::DEFAULT_ENDPOINT_REL_URL);
$this->SetLastQueries($aLastClasses);
}
/**
* Set the search form endpoint URL.
* If $bRelativeUrl is true, then $sEndpoint will be complete with the app_root_url
*
* @param string $sEndpoint URL to the endpoint
* @param bool $bRelativeUrl Whether or not the $sEndpoint parameter is a relative URL
*
* @return $this
* @throws \Exception
*/
public function SetEndpoint($sEndpoint, $bRelativeUrl = true)
{
$this->sEndpoint = (($bRelativeUrl) ? utils::GetAbsoluteUrlAppRoot() : '') . $sEndpoint;
return $this;
}
/**
* Return the absolute URL of the search form
*
* @return $this
* @throws \Exception
*/
public function GetEndpoint()
{
return $this->sEndpoint;
}
/**
* Set all the last queries at once
*
* @param array $aLastQueries
*
* @return $this
*/
public function SetLastQueries($aLastQueries)
{
$this->aLastQueries = $aLastQueries;
return $this;
}
/**
* Return the last queries (query itself, label as HTML)
*
* @return array
*/
public function GetLastQueries()
{
return $this->aLastQueries;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\GlobalSearch;
/**
* Class GlobalSearchFactory
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\GlobalSearch
* @internal
* @since 2.8.0
*/
class GlobalSearchFactory
{
/**
* Make a GlobalSearch component with the history entries from the current user
*
* @return \Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \Exception
*/
public static function MakeFromUserHistory()
{
$aLastClasses = GlobalSearchHelper::GetLastQueries();
return new GlobalSearch(GlobalSearch::BLOCK_CODE, $aLastClasses);
}
}

View File

@@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\GlobalSearch;
namespace Combodo\iTop\Application\UI\Component\GlobalSearch;
use appUserPreferences;
@@ -27,7 +27,8 @@ use utils;
* Class GlobalSearchHelper
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\GlobalSearch
* @package Combodo\iTop\Application\UI\Component\GlobalSearch
* @internal
* @since 2.8.0
*/
class GlobalSearchHelper

View File

@@ -0,0 +1,224 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\PopoverMenu;
use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem;
use Combodo\iTop\Application\UI\UIBlock;
use Exception;
/**
* Class PopoverMenu
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\PopoverMenu
* @internal
* @since 2.8.0
*/
class PopoverMenu extends UIBlock
{
const BLOCK_CODE = 'ibo-popover-menu';
const HTML_TEMPLATE_REL_PATH = 'components/popover-menu/layout';
//const JS_TEMPLATE_REl_PATH = 'components/popover-menu/layout';
const JS_FILES_REL_PATH = [
'js/components/popover-menu.js',
];
/** @var array $aSections */
protected $aSections;
/**
* PopoverMenu constructor.
*
* @param string|null $sId
*/
public function __construct($sId = null)
{
parent::__construct($sId);
$this->aSections = [];
}
/**
* Add a section $sId if not already existing.
* Important: It does not reset the section.
*
* @param string $sId
*
* @return $this
*/
public function AddSection($sId)
{
if (false === $this->HasSection($sId))
{
$this->aSections[$sId] = [
'aItems' => [],
];
}
return $this;
}
/**
* Remove the $sId section.
* Note: If the section does not exist, we silently proceed anyway.
*
* @param string $sId
*
* @return $this
* @throws \Exception
*/
public function RemoveSection($sId)
{
if (true === $this->HasSection($sId))
{
unset($this->aSections[$sId]);
}
return $this;
}
/**
* Return true if the $sId section exists
*
* @param string $sId
*
* @return bool
*/
public function HasSection($sId)
{
return array_key_exists($sId, $this->aSections);
}
/**
* Clear the $sId section from all its items.
*
* @param string $sId
*
* @return $this
* @throws \Exception
*/
public function ClearSection($sId)
{
if (false === $this->HasSection($sId))
{
throw new Exception('Could not clear section "'.$sId.'" as it does not exist in the "'.$this->GetId().'" menu');
}
$this->aSections[$sId]['aItems'] = [];
return $this;
}
/**
* Return the sections
*
* @return array
*/
public function GetSections()
{
return $this->aSections;
}
/**
* Add the $oItem in the $sSectionId. If an item with the same ID already exists it will be overwritten.
*
* @param string $sSectionId
* @param \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem $oItem
*
* @return $this
* @throws \Exception
*/
public function AddItem($sSectionId, PopoverMenuItem $oItem)
{
if(false === $this->HasSection($sSectionId))
{
throw new Exception('Could not add an item to the "'.$sSectionId.'" section has it does not seem to exist in the "'.$this->GetId().'" menu.');
}
$this->aSections[$sSectionId]['aItems'][$oItem->GetId()] = $oItem;
return $this;
}
/**
* Remove the $sItemId from the $sSectionId.
* Note: If the item is not in the section, we proceed silently.
*
* @param string $sSectionId
* @param string $sItemId
*
* @return $this
* @throws \Exception
*/
public function RemoveItem($sSectionId, $sItemId)
{
if(false === $this->HasSection($sSectionId))
{
throw new Exception('Could not remove en item from the "'.$sSectionId.'" as it does not seem to exist in the "'.$this->GetId().'" menu.');
}
if(array_key_exists($sItemId, $this->aSections[$sSectionId]['aItems']))
{
unset($this->aSections[$sSectionId]['aItems'][$sItemId]);
}
return $this;
}
/**
* Set all $aItems at once in the $sSectionId, overwriting all existing.
*
* @param string $sSectionId
* @param PopoverMenuItem[] $aItems
*
* @return $this
* @throws \Exception
*/
public function SetItems($sSectionId, $aItems)
{
if(false === $this->HasSection($sSectionId))
{
throw new Exception('Could not set items to the "'.$sSectionId.'" section has it does not seem to exist in the "'.$this->GetId().'" menu.');
}
$this->aSections[$sSectionId]['aItems'] = $aItems;
return $this;
}
/**
* @inheritDoc
*/
public function GetSubBlocks()
{
$aSubBlocks = [];
foreach($this->aSections as $sSectionId => $aSectionData)
{
foreach($aSectionData['aItems'] as $sItemId => $oItem)
{
$aSubBlocks[$sItemId] = $oItem;
}
}
return $aSubBlocks;
}
}

View File

@@ -0,0 +1,197 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\PopoverMenu;
use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopupMenuItemFactory;
use Dict;
use JSPopupMenuItem;
use MetaModel;
use URLPopupMenuItem;
use UserRights;
use utils;
/**
* Class PopoverMenuFactory
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\PopoverMenu
* @internal
* @since 2.8.0
*/
class PopoverMenuFactory
{
/**
* Make a standard NavigationMenu layout for backoffice pages
*
* @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu
* @throws \CoreException
* @throws \Exception
*/
public static function MakeUserMenuForNavigationMenu()
{
$oMenu = new PopoverMenu('ibo-navigation-menu--user-menu');
// Allowed portals
$aAllowedPortalsItems = static::PrepareAllowedPortalsItemsForUserMenu();
if(!empty($aAllowedPortalsItems))
{
$oMenu->AddSection('allowed_portals')
->SetItems('allowed_portals', $aAllowedPortalsItems);
}
// User related pages
$oMenu->AddSection('user_related')
->SetItems('user_related', static::PrepareUserRelatedItemsForUserMenu());
// Misc links
$oMenu->AddSection('misc')
->SetItems('misc', static::PrepareMiscItemsForUserMenu());
return $oMenu;
}
/**
* Return the allowed portals items for the current user
*
* @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem[]
*/
protected static function PrepareAllowedPortalsItemsForUserMenu()
{
$aItems = [];
foreach (UserRights::GetAllowedPortals() as $aAllowedPortal)
{
if ($aAllowedPortal['id'] !== 'backoffice')
{
$oPopupMenuItem = new URLPopupMenuItem(
'portal:'.$aAllowedPortal['id'],
Dict::S($aAllowedPortal['label']),
$aAllowedPortal['url'],
'_blank'
);
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem($oPopupMenuItem);
}
}
return $aItems;
}
/**
* Return the user related items (preferences, change password, log off, ...)
*
* @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem[]
* @throws \CoreException
*/
protected static function PrepareUserRelatedItemsForUserMenu()
{
$aItems = [];
// Preferences
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new URLPopupMenuItem(
'UI:Preferences',
Dict::S('UI:Preferences'),
utils::GetAbsoluteUrlAppRoot().'pages/preferences.php'
)
);
// Archive mode
if(true === utils::IsArchiveMode())
{
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new JSPopupMenuItem(
'UI:ArchiveModeOff',
Dict::S('UI:ArchiveModeOff'),
'return ArchiveMode(false);'
)
);
}
elseif(UserRights::CanBrowseArchive())
{
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new JSPopupMenuItem(
'UI:ArchiveModeOn',
Dict::S('UI:ArchiveModeOn'),
'return ArchiveMode(true);'
)
);
}
// Logoff
if(utils::CanLogOff())
{
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new URLPopupMenuItem(
'UI:LogOffMenu',
Dict::S('UI:LogOffMenu'),
utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff'
)
);
}
// Change password
if (UserRights::CanChangePassword())
{
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new URLPopupMenuItem(
'UI:ChangePwdMenu',
Dict::S('UI:ChangePwdMenu'),
utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=change_pwd'
)
);
}
// TODO: iPopupMenuExtension::MENU_USER_ACTIONS
return $aItems;
}
/**
* Return the misc. items for the user menu (online doc., about box)
*
* @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem[]
*/
protected static function PrepareMiscItemsForUserMenu()
{
$aItems = [];
// Online documentation
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new URLPopupMenuItem(
'UI:Help',
Dict::S('UI:Help'),
MetaModel::GetConfig()->Get('online_help'),
'_blank'
)
);
// About box
$aItems[] = PopupMenuItemFactory::MakeFromApplicationPopupMenuItem(
new JSPopupMenuItem(
'UI:AboutBox',
Dict::S('UI:AboutBox'),
'return ShowAboutBox();'
)
);
return $aItems;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\PopoverMenu\PopoverMenuItem;
/**
* Class JsPopoverMenuItem
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem
* @since 2.8.0
*/
class JsPopoverMenuItem extends PopoverMenuItem
{
const HTML_TEMPLATE_REL_PATH = 'components/popover-menu/item/mode_js';
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\PopoverMenu\PopoverMenuItem;
use ApplicationPopupMenuItem;
use Combodo\iTop\Application\UI\UIBlock;
/**
* Class PopoverMenuItem
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem
* @internal
* @since 2.8.0
*/
class PopoverMenuItem extends UIBlock
{
const BLOCK_CODE = 'ibo-popover-menu--item';
const HTML_TEMPLATE_REL_PATH = 'components/popover-menu/item/layout';
/** @var \ApplicationPopupMenuItem $oPopupMenuItem We decorate the class with the original \ApplicationPopupMenuItem as it is used among the application (backoffice, portal, extensions) and cannot be refactored without BC breaks */
protected $oPopupMenuItem;
/**
* PopoverMenuItem constructor.
*
* @param \ApplicationPopupMenuItem $oPopupMenuItem
*/
public function __construct(ApplicationPopupMenuItem $oPopupMenuItem)
{
$this->oPopupMenuItem = $oPopupMenuItem;
parent::__construct(/* ID will be generated from $oPopupMenuItem */);
}
/**
* @inheritDoc
*/
protected function GenerateId()
{
return static::BLOCK_CODE.'-'.$this->oPopupMenuItem->GetUID();
}
/**
* @see \ApplicationPopupMenuItem::GetLabel()
* @return string
*/
public function GetLabel()
{
return $this->oPopupMenuItem->GetLabel();
}
/**
* @see \ApplicationPopupMenuItem::SetCssClasses()
* @param array $aCssClasses
*
* @return $this
*/
public function SetCssClasses($aCssClasses)
{
$this->oPopupMenuItem->SetCssClasses($aCssClasses);
return $this;
}
/**
* @see \ApplicationPopupMenuItem::AddCssClass()
* @param string $sCssClass
*
* @return $this
*/
public function AddCssClass($sCssClass)
{
$this->oPopupMenuItem->AddCssClass($sCssClass);
return $this;
}
/**
* @see \ApplicationPopupMenuItem::GetCssClasses()
* @return array
*/
public function GetCssClasses()
{
return $this->oPopupMenuItem->GetCssClasses();
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\PopoverMenu\PopoverMenuItem;
use ApplicationPopupMenuItem;
use JSPopupMenuItem;
use URLPopupMenuItem;
/**
* Class PopupMenuItemFactory
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem
* @internal
* @since 2.8.0
*/
class PopupMenuItemFactory
{
/**
* Make a standard NavigationMenu layout for backoffice pages
*
* @param \ApplicationPopupMenuItem $oItem
*
* @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem
*/
public static function MakeFromApplicationPopupMenuItem(ApplicationPopupMenuItem $oItem)
{
$sNamespace = 'Combodo\\iTop\\Application\\UI\\Component\\PopoverMenu\\PopoverMenuItem\\';
switch(true)
{
case $oItem instanceof URLPopupMenuItem:
$sTargetClass = 'UrlPopoverMenuItem';
break;
case $oItem instanceof JSPopupMenuItem:
$sTargetClass = 'JsPopoverMenuItem';
break;
default:
$sTargetClass = 'PopoverMenuItem';
break;
}
$sTargetClass = $sNamespace.$sTargetClass;
return new $sTargetClass($oItem);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\PopoverMenu\PopoverMenuItem;
/**
* Class UrlPopoverMenuItem
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem
* @since 2.8.0
*/
class UrlPopoverMenuItem extends PopoverMenuItem
{
const HTML_TEMPLATE_REL_PATH = 'components/popover-menu/item/mode_url';
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\QuickCreate;
use Combodo\iTop\Application\UI\UIBlock;
use UserRights;
/**
* Class QuickCreate
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\QuickCreate
* @internal
* @since 2.8.0
*/
class QuickCreate extends UIBlock
{
const BLOCK_CODE = 'ibo-quick-create';
const HTML_TEMPLATE_REL_PATH = 'components/quick-create/layout';
const JS_TEMPLATE_REL_PATH = 'components/quick-create/layout';
const JS_FILES_REL_PATH = [
'js/selectize.min.js',
'js/components/quick-create.js',
];
const CSS_FILES_REL_PATH = [
'css/selectize.default.css',
];
const DEFAULT_ENDPOINT_REL_URL = 'pages/UI.php';
/** @var array $aAvailableClasses */
protected $aAvailableClasses;
/** @var array $aLastClasses */
protected $aLastClasses;
/**
* QuickCreate constructor.
*
* @param string $sId
* @param array $aLastClasses
*
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function __construct($sId = null, $aLastClasses = [])
{
parent::__construct($sId);
$this->aAvailableClasses = UserRights::GetAllowedClasses(UR_ACTION_CREATE, array('bizmodel'), true);
$this->aLastClasses = $aLastClasses;
}
/**
* Return the available classes (to create) for the current user
*
* @return array
*/
public function GetAvailableClasses()
{
return $this->aAvailableClasses;
}
/**
* Set all the last classes at once
*
* @param array $aLastClasses
*
* @return $this
*/
public function SetLastClasses($aLastClasses)
{
$this->aLastClasses = $aLastClasses;
return $this;
}
/**
* Return the last classes (class name, label as HTML, icon URL, ...)
*
* @return array
*/
public function GetLastClasses()
{
return $this->aLastClasses;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Component\QuickCreate;
/**
* Class QuickCreateFactory
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Component\QuickCreate
* @internal
* @since 2.8.0
*/
class QuickCreateFactory
{
/**
* Make a QuickCreate component with the last classes from the current user
*
* @return \Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function MakeFromUserHistory()
{
$aLastClasses = QuickCreateHelper::GetLastClasses();
return new QuickCreate(QuickCreate::BLOCK_CODE,$aLastClasses);
}
}

View File

@@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\QuickCreate;
namespace Combodo\iTop\Application\UI\Component\QuickCreate;
use appUserPreferences;
@@ -29,7 +29,8 @@ use utils;
* Class QuickCreateHelper
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\QuickCreate
* @package Combodo\iTop\Application\UI\Component\QuickCreate
* @internal
* @since 2.8.0
*/
class QuickCreateHelper

View File

@@ -0,0 +1,222 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Layout\NavigationMenu;
use ApplicationContext;
use ApplicationMenu;
use appUserPreferences;
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\UIBlock;
use Dict;
use MetaModel;
use UserRights;
use utils;
/**
* Class NavigationMenu
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Layout\NavigationMenu
* @internal
* @since 2.8.0
*/
class NavigationMenu extends UIBlock
{
const BLOCK_CODE = 'ibo-navigation-menu';
const HTML_TEMPLATE_REL_PATH = 'layouts/navigation-menu/layout';
const JS_TEMPLATE_REL_PATH = 'layouts/navigation-menu/layout';
const JS_FILES_REL_PATH = [
'js/layouts/navigation-menu.js',
];
/** @var string $sAppRevisionNumber */
protected $sAppRevisionNumber;
/** @var string $sAppSquareIconUrl */
protected $sAppSquareIconUrl;
/** @var string $sAppFullIconUrl */
protected $sAppFullIconUrl;
/** @var string $sAppIconLink */
protected $sAppIconLink;
/** @var array $aMenuGroups */
protected $aMenuGroups;
/** @var array $aUserData */
protected $aUserData;
/** @var \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu $oUserMenu */
private $oUserMenu;
/** @var bool $bIsExpanded */
protected $bIsExpanded;
/**
* NavigationMenu constructor.
*
* @param string|null $sId
* @param \ApplicationContext $oAppContext
* @param \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu $oUserMenu
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \Exception
*/
public function __construct($sId, ApplicationContext $oAppContext, PopoverMenu $oUserMenu)
{
parent::__construct($sId);
$this->sAppRevisionNumber = utils::GetAppRevisionNumber();
$this->sAppSquareIconUrl = Branding::GetSquareMainLogoAbsoluteUrl();
$this->sAppFullIconUrl = Branding::GetFullMainLogoAbsoluteUrl();
$this->sAppIconLink = MetaModel::GetConfig()->Get('app_icon_url');
$this->aMenuGroups = ApplicationMenu::GetMenuGroups($oAppContext->GetAsHash());
$this->oUserMenu = $oUserMenu;
$this->ComputeExpandedState();
$this->ComputeUserData();
}
/**
* @return string
*/
public function GetAppRevisionNumber()
{
return $this->sAppRevisionNumber;
}
/**
* @return string
*/
public function GetAppSquareIconUrl()
{
return $this->sAppSquareIconUrl;
}
/**
* @return string
*/
public function GetAppFullIconUrl()
{
return $this->sAppFullIconUrl;
}
/**
* @return string
*/
public function GetAppIconLink()
{
return $this->sAppIconLink;
}
/**
* @return array
*/
public function GetMenuGroups()
{
return $this->aMenuGroups;
}
/**
* @return array
*/
public function GetUserData()
{
return $this->aUserData;
}
/**
* @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu
*/
public function GetUserMenu()
{
return $this->oUserMenu;
}
/**
* Return true if the menu is expanded
*
* @return bool
*/
public function IsExpanded()
{
return $this->bIsExpanded;
}
/**
* @inheritDoc
*/
public function GetSubBlocks()
{
return [$this->oUserMenu->GetId() => $this->oUserMenu];
}
/**
* Compute if the menu is expanded or collapsed
*
* @return $this
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
protected function ComputeExpandedState()
{
$bIsExpanded = false;
// Check if menu should be opened only if we re not in demo mode
if (false === MetaModel::GetConfig()->Get('demo_mode'))
{
if (utils::ReadParam('force_menu_pane', null) === 0)
{
$bIsExpanded = false;
}
elseif (appUserPreferences::GetPref('menu_pane', 'closed') === 'opened')
{
$bIsExpanded = true;
}
}
$this->bIsExpanded = $bIsExpanded;
return $this;
}
/**
* Compute the user data displayed in the menu (organization, name, picture, ...)
*
* @return $this
* @throws \Exception
*/
protected function ComputeUserData()
{
$aData = [
'sOrganization' => UserRights::GetContactOrganizationFriendlyname(),
'sFirstname' => UserRights::GetContactFirstname(),
'sPictureUrl' => UserRights::GetContactPicture(),
];
// Logon message
$sLogonMessageDictCode = (UserRights::IsAdministrator()) ? 'UI:LoggedAsMessage+Admin' : 'UI:LoggedAsMessage';
$aData['sLogonMessage'] = Dict::Format($sLogonMessageDictCode, UserRights::GetUser());
$this->aUserData = $aData;
return $this;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Layout\NavigationMenu;
use ApplicationContext;
use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuFactory;
/**
* Class NavigationMenuFactory
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Layout\NavigationMenu
* @internal
* @since 2.8.0
*/
class NavigationMenuFactory
{
/**
* Make a standard NavigationMenu layout for backoffice pages
*
* @return \Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenu
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
*/
public static function MakeStandard()
{
return new NavigationMenu(
NavigationMenu::BLOCK_CODE,
new ApplicationContext(),
PopoverMenuFactory::MakeUserMenuForNavigationMenu()
);
}
}

View File

@@ -0,0 +1,185 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Layout\TopBar;
use Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs;
use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch;
use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate;
use Combodo\iTop\Application\UI\UIBlock;
/**
* Class TopBar
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Layout\TopBar
* @internal
* @since 2.8.0
*/
class TopBar extends UIBlock
{
const BLOCK_CODE = 'ibo-top-bar';
const HTML_TEMPLATE_REL_PATH = 'layouts/top-bar/layout';
/** @var QuickCreate|null $oQuickCreate */
protected $oQuickCreate;
/** @var GlobalSearch|null $oGlobalSearch */
protected $oGlobalSearch;
/** @var Breadcrumbs|null $oBreadcrumbs */
protected $oBreadcrumbs;
/**
* TopBar constructor.
*
* @param string $sId
* @param \Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate $oQuickCreate
* @param \Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch $oGlobalSearch
* @param \Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs $oBreadcrumbs
*/
public function __construct($sId = null, QuickCreate $oQuickCreate = null, GlobalSearch $oGlobalSearch = null, Breadcrumbs $oBreadcrumbs = null)
{
parent::__construct($sId);
$this->oQuickCreate = $oQuickCreate;
$this->oGlobalSearch = $oGlobalSearch;
$this->oBreadcrumbs = $oBreadcrumbs;
}
/**
* Set the quick create component
*
* @param \Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate $oQuickCreate
*
* @return $this
*/
public function SetQuickCreate(QuickCreate $oQuickCreate)
{
$this->oQuickCreate = $oQuickCreate;
return $this;
}
/**
* Return the global search component
*
* @return \Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate|null
*/
public function GetQuickCreate()
{
return $this->oQuickCreate;
}
/**
* Return true if the quick create has been set
*
* @return bool
*/
public function HasQuickCreate()
{
return ($this->oQuickCreate !== null);
}
/**
* Set the global search component
*
* @param \Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch $oGlobalSearch
*
* @return $this
*/
public function SetGlobalSearch(GlobalSearch $oGlobalSearch)
{
$this->oGlobalSearch = $oGlobalSearch;
return $this;
}
/**
* Return the global search component
*
* @return \Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch|null
*/
public function GetGlobalSearch()
{
return $this->oGlobalSearch;
}
/**
* Return true if the global search has been set
*
* @return bool
*/
public function HasGlobalSearch()
{
return ($this->oGlobalSearch !== null);
}
/**
* Set the breadcrumbs component
*
* @param \Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs $oBreadcrumbs
*
* @return $this
*/
public function SetBreadcrumbs(Breadcrumbs $oBreadcrumbs)
{
$this->oBreadcrumbs = $oBreadcrumbs;
return $this;
}
/**
* Return the breadcrumbs component
*
* @return \Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs|null
*/
public function GetBreadcrumbs()
{
return $this->oBreadcrumbs;
}
/**
* Return true if the breadcrumb has been set
*
* @return bool
*/
public function HasBreadcrumbs()
{
return ($this->oBreadcrumbs !== null);
}
/**
* @inheritDoc
*/
public function GetSubBlocks()
{
$aSubBlocks = [];
$aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs'];
foreach($aSubBlocksNames as $sSubBlockName)
{
$sHasMethodName = 'Has'.$sSubBlockName;
if(true === call_user_func_array([$this, $sHasMethodName], []))
{
$sPropertyName = 'o'.$sSubBlockName;
$aSubBlocks[$this->$sPropertyName->GetId()] = $this->$sPropertyName;
}
}
return $aSubBlocks;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI\Layout\TopBar;
use Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs;
use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearchFactory;
use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreateFactory;
use utils;
/**
* Class TopBarFactory
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\UI\Layout\TopBar
* @internal
* @since 2.8.0
*/
class TopBarFactory
{
/**
* Make a standard TopBar layout for backoffice pages
*
* @param array|null $aBreadcrumbsEntry Current breadcrumbs entry to add
*
* @return \Combodo\iTop\Application\UI\Layout\TopBar\TopBar
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function MakeStandard($aBreadcrumbsEntry = null)
{
$oTopBar = new TopBar();
if(utils::GetConfig()->Get('quick_create.enabled') === true)
{
$oTopBar->SetQuickCreate(QuickCreateFactory::MakeFromUserHistory());
}
if(utils::GetConfig()->Get('global_search.enabled') === true)
{
$oTopBar->SetGlobalSearch(GlobalSearchFactory::MakeFromUserHistory());
}
if(utils::GetConfig()->Get('breadcrumb.enabled') === true)
{
$oBreadcrumbs = new Breadcrumbs(Breadcrumbs::BLOCK_CODE, $aBreadcrumbsEntry);
$oTopBar->SetBreadcrumbs($oBreadcrumbs);
}
return $oTopBar;
}
}

View File

@@ -0,0 +1,196 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI;
use utils;
/**
* Class UIBlock
*
* @package Combodo\iTop\Application\UI
* @internal
* @since 2.8.0
*/
abstract class UIBlock implements iUIBlock
{
/** @var string BLOCK_CODE The block code to use to generate the identifier, the CSS/JS prefixes, ... */
const BLOCK_CODE = 'ibo-block';
/** @var string|null GLOBAL_TEMPLATE_REL_PATH Relative path (from <ITOP>/templates/) to the "global" TWIG template which contains HTML, JS inline, JS files, CSS inline, CSS files. Should not be used to often as JS/CSS files would be duplicated making browser parsing time way longer. */
const GLOBAL_TEMPLATE_REL_PATH = null;
/** @var string|null HTML_TEMPLATE_REL_PATH Relative path (from <ITOP>/templates/) to the HTML template */
const HTML_TEMPLATE_REL_PATH = null;
/** @var array JS_FILES_REL_PATH Relative paths (from <ITOP>/) to the JS files */
const JS_FILES_REL_PATH = [];
/** @var string|null JS_TEMPLATE_REL_PATH Relative path (from <ITOP>/templates/) to the JS template */
const JS_TEMPLATE_REL_PATH = null;
/** @var array CSS_FILES_REL_PATH Relative paths (from <ITOP>/) to the CSS files */
const CSS_FILES_REL_PATH = [];
/** @var string|null CSS_TEMPLATE_REL_PATH Relative path (from <ITOP>/templates/) to the CSS template */
const CSS_TEMPLATE_REL_PATH = null;
/** @var string ENUM_BLOCK_FILES_TYPE_JS */
const ENUM_BLOCK_FILES_TYPE_JS = 'js';
/** @var string ENUM_BLOCK_FILES_TYPE_CSS */
const ENUM_BLOCK_FILES_TYPE_CSS = 'css';
/**
* @inheritDoc
*/
public static function GetGlobalTemplateRelPath()
{
return static::GLOBAL_TEMPLATE_REL_PATH;
}
/**
* @inheritDoc
*/
public static function GetHtmlTemplateRelPath()
{
return static::HTML_TEMPLATE_REL_PATH;
}
/**
* @inheritDoc
*/
public static function GetJsTemplateRelPath()
{
return static::JS_TEMPLATE_REL_PATH;
}
/**
* @inheritDoc
*/
public static function GetJsFilesRelPaths()
{
return static::JS_FILES_REL_PATH;
}
/**
* @inheritDoc
*/
public static function GetCssTemplateRelPath()
{
return static::CSS_TEMPLATE_REL_PATH;
}
/**
* @inheritDoc
*/
public static function GetCssFilesRelPaths()
{
return static::CSS_FILES_REL_PATH;
}
/** @var string $sId */
protected $sId;
/**
* UIBlock constructor.
*
* @param string|null $sId
*/
public function __construct($sId = null)
{
$this->sId = ($sId !== null) ? $sId : $this->GenerateId();
}
/**
* Return a unique ID for the block
*
* @return string
*/
protected function GenerateId()
{
return uniqid(static::BLOCK_CODE.'-');
}
/**
* @inheritDoc
*/
public function GetId()
{
return $this->sId;
}
/**
* @inheritDoc
* @return \Combodo\iTop\Application\UI\UIBlock[]
*/
public function GetSubBlocks()
{
return [];
}
/**
* @inheritDoc
* @throws \Exception
*/
public function GetJsFilesUrlRecursively($bAbsoluteUrl = false)
{
return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_JS, $bAbsoluteUrl);
}
/**
* @inheritDoc
* @throws \Exception
*/
public function GetCssFilesUrlRecursively($bAbsoluteUrl = false)
{
return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_CSS, $bAbsoluteUrl);
}
/**
* Return an array of the URL of the block $sFilesType and its sub blocks.
* URL is relative unless the $bAbsoluteUrl is set to true.
*
* @param string $sFilesType (see static::ENUM_BLOCK_FILES_TYPE_JS, static::ENUM_BLOCK_FILES_TYPE_CSS)
* @param bool $bAbsoluteUrl
*
* @return array
* @throws \Exception
*/
protected function GetFilesUrlRecursively($sFilesType, $bAbsoluteUrl = false)
{
$aFiles = [];
$sFilesRelPathMethodName = 'Get'.ucfirst($sFilesType).'FilesRelPaths';
$sFilesAbsUrlMethodName = 'Get'.ucfirst($sFilesType).'FilesUrlRecursively';
// Files from the block itself
foreach($this::$sFilesRelPathMethodName() as $sFilePath)
{
$aFiles[] = (($bAbsoluteUrl === true) ? utils::GetAbsoluteUrlAppRoot() : '').$sFilePath;
}
// Files from its sub blocks
foreach($this->GetSubBlocks() as $sSubBlockName => $oSubBlock)
{
$aFiles = array_merge(
$aFiles,
call_user_func_array([$oSubBlock, $sFilesAbsUrlMethodName], [$bAbsoluteUrl])
);
}
return $aFiles;
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* Copyright (C) 2013-2020 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\Application\UI;
/**
* Interface iUIBlock
*
* @package Combodo\iTop\Application\UI
* @internal
* @since 2.8.0
*/
interface iUIBlock
{
/**
* Return the relative path (from <ITOP>/templates/) of the global template (HTML, JS, CSS) to use or null if it's not provided. Should not be used to often as JS/CSS files would be duplicated making the browser parsing time way longer.
*
* @return string|null
*/
public static function GetGlobalTemplateRelPath();
/**
* Return the relative path (from <ITOP>/templates/) of the HTML template to use or null if no HTML to render
*
* @return string|null
*/
public static function GetHtmlTemplateRelPath();
/**
* Return the relative path (from <ITOP>/templates/) of the JS template to use or null if there is no inline JS to render
*
* @return string|null
*/
public static function GetJsTemplateRelPath();
/**
* Return an array of the relative paths (from <ITOP>/) of the JS files to use
*
* @return array
*/
public static function GetJsFilesRelPaths();
/**
* Return the relative path (from <ITOP>/templates/) of the CSS template to use or null if there is no inline CSS to render
*
* @return string|null
*/
public static function GetCssTemplateRelPath();
/**
* Return an array of the relative paths (from <ITOP>/) of the CSS files to use
*
* @return array
*/
public static function GetCssFilesRelPaths();
/**
* Return the ID of the block
*
* @return string
*/
public function GetId();
/**
* Return an array iUIBlock embedded in this iUIBlock
*
* @return \Combodo\iTop\Application\UI\iUIBlock[]
*/
public function GetSubBlocks();
/**
* Return an array of the JS files URL of the block and its sub blocks.
* URL is relative unless the $bAbsolutePath is set to true.
*
* @param bool $bAbsoluteUrl
*
* @return array
*/
public function GetJsFilesUrlRecursively($bAbsoluteUrl = false);
/**
* Return an array of the CSS files URL of the block and its sub blocks.
* URL is relative unless the $bAbsolutePath is set to true.
*
* @param bool $bAbsoluteUrl
*
* @return array
* @throws \Exception
*/
public function GetCssFilesUrlRecursively($bAbsoluteUrl = false);
}

View File

@@ -1 +1 @@
<div id="{{ aBreadCrumbs.sId }}" class="ibo-breadcrumbs"></div>
<div id="{{ oBreadcrumbs.Id }}" class="ibo-breadcrumbs"></div>

View File

@@ -0,0 +1,6 @@
// TODO: We need to find a clean way to launch this script only once the JS scripts are loaded
document.addEventListener("DOMContentLoaded", function(){
setTimeout(function(){
$('#{{ oBreadcrumbs.Id }}').breadcrumbs({{ oBreadcrumbs.JsWidgetOptions|json_encode|raw }});
}, 500);
});

View File

@@ -1,5 +1,5 @@
<div id="{{ aGlobalSearch.sId }}" class="ibo-global-search" data-role="ibo-global-search">
<form action="{{ aGlobalSearch.sEndpoint }}" method="post" class="ibo-global-search--head" data-role="ibo-global-search--head">
<div id="{{ oGlobalSearch.Id }}" class="ibo-global-search" data-role="ibo-global-search">
<form action="{{ oGlobalSearch.Endpoint }}" method="post" class="ibo-global-search--head" data-role="ibo-global-search--head">
<a href="#" class="ibo-global-search--icon fas fa-search" data-role="ibo-global-search--icon" data-tooltip-content="{{ 'UI:Component:GlobalSearch:Tooltip'|dict_s }}" data-tooltip-placement="bottom-start" data-tooltip-distance-offset="25"></a>
<input type="text" name="query" class="ibo-global-search--input" data-role="ibo-global-search--input" placeholder="{{ 'UI:Component:GlobalSearch:Input:Placeholder'|dict_s }}" />
</form>
@@ -9,8 +9,8 @@
<span>{{ 'UI:Component:GlobalSearch:Recents:Title'|dict_s }}</span>
</div>
<div class="ibo-global-search--compartment-content" data-role="ibo-global-search--compartment-content">
{% if aGlobalSearch.aLastQueries|length > 0 %}
{% for aQuery in aGlobalSearch.aLastQueries %}
{% if oGlobalSearch.LastQueries|length > 0 %}
{% for aQuery in oGlobalSearch.LastQueries %}
<a href="#" class="ibo-global-search--compartment-element" data-role="ibo-global-search--compartment-element" data-query-raw="{{ aQuery.query }}" title="{{ aQuery.query }}">
{% if aQuery.icon_url is defined %}
<img src="{{ aPage.sAbsoluteUrlAppRoot ~ aQuery.icon_url}}" class="ibo-global-search--compartment-element-image" />

View File

@@ -0,0 +1,6 @@
// TODO: We need to find a clean way to launch this script only once the JS scripts are loaded
document.addEventListener("DOMContentLoaded", function(){
setTimeout(function(){
$('#{{ oGlobalSearch.Id }}').global_search();
}, 500);
});

View File

@@ -0,0 +1 @@
<div>Item: {{ oPopoverMenuItem.Id }}</div>

View File

@@ -0,0 +1,13 @@
<div {% if oPopoverMenu.Id is defined %}id="{{ oPopoverMenu.Id }}"{% endif %} class="ibo-popover-menu" data-role="ibo-popover-menu">
<div class="ibo-popover-menu--menu">
{% for aSection in oPopoverMenu.Sections %}
{% if aSection.aItems|length > 0 %}
<div class="ibo-popover-menu--section">
{% for oPopoverMenuItem in aSection.aItems %}
{{ render_block(oPopoverMenuItem) }}
{% endfor %}
</div>
{% endif %}
{% endfor %}
</div>
</div>

View File

@@ -1,10 +1,10 @@
<div id="{{ aQuickCreate.sId }}" class="ibo-quick-create" data-role="ibo-quick-create">
<form action="{{ aQuickCreate.sEndpoint }}" method="get" class="ibo-quick-create--head" data-role="ibo-quick-create--head">
<div id="{{ oQuickCreate.Id }}" class="ibo-quick-create" data-role="ibo-quick-create">
<form action="{{ oQuickCreate.Endpoint }}" method="get" class="ibo-quick-create--head" data-role="ibo-quick-create--head">
<input type="hidden" name="operation" value="new" />
<a href="#" class="ibo-quick-create--icon fas fa-plus" data-role="ibo-quick-create--icon" data-tooltip-content="{{ 'UI:Component:QuickCreate:Tooltip'|dict_s }}" data-tooltip-placement="bottom-start" data-tooltip-distance-offset="25"></a>
<select name="class" class="ibo-quick-create--input" data-role="ibo-quick-create--input" placeholder="{{ 'UI:Component:QuickCreate:Input:Placeholder'|dict_s }}">
<option value="">{{ 'UI:Component:QuickCreate:Input:Placeholder'|dict_s }}</option>
{% for sClassCode, sClassLabel in aQuickCreate.aAvailableClasses %}
{% for sClassCode, sClassLabel in oQuickCreate.AvailableClasses %}
<option value="{{ sClassCode }}">{{ sClassLabel }}</option>
{% endfor %}
</select>
@@ -15,8 +15,8 @@
<span>{{ 'UI:Component:QuickCreate:Recents:Title'|dict_s }}</span>
</div>
<div class="ibo-quick-create--compartment-content" data-role="ibo-quick-create--compartment-content">
{% if aQuickCreate.aLastClasses|length > 0 %}
{% for aClass in aQuickCreate.aLastClasses %}
{% if oQuickCreate.LastClasses|length > 0 %}
{% for aClass in oQuickCreate.LastClasses %}
<a href="{{ aClass.target_url }}" class="ibo-quick-create--compartment-element" data-role="ibo-quick-create--compartment-element" data-class-code="{{ aQuery.class }}">
{% if aClass.icon_url is defined %}
<img src="{{ aClass.icon_url}}" class="ibo-quick-create--compartment-element-image" />

View File

@@ -0,0 +1,6 @@
// TODO: We need to find a clean way to launch this script only once the JS scripts are loaded
document.addEventListener("DOMContentLoaded", function(){
setTimeout(function(){
$('#{{ oQuickCreate.Id }}').quick_create();
}, 500);
});

View File

@@ -1,8 +1,8 @@
<nav id="{{ aNavigationMenu.sId }}" class="ibo-navigation-menu {% if aNavigationMenu.bIsExpanded == true %}ibo-navigation-menu--is-expanded{% endif %}">
<nav id="{{ oNavigationMenu.Id }}" class="ibo-navigation-menu {% if NavigationMenu.IsExpanded == true %}ibo-navigation-menu--is-expanded{% endif %}">
<div class="ibo-navigation-menu--body">
<div class="ibo-navigation-menu--top-part">
<a class="ibo-navigation-menu--square-company-logo" title="{{ aNavigationMenu.sAppRevisionNumber }}" href="{{ aNavigationMenu.sAppIconLink }}">
<img src="{{ aNavigationMenu.sAppSquareIconUrl }}" alt="{{ 'UI:Layout:NavigationMenu:CompanyLogo:AltText'|dict_s }}" />
<a class="ibo-navigation-menu--square-company-logo" title="{{ oNavigationMenu.AppRevisionNumber }}" href="{{ oNavigationMenu.AppIconLink }}">
<img src="{{ oNavigationMenu.AppSquareIconUrl }}" alt="{{ 'UI:Layout:NavigationMenu:CompanyLogo:AltText'|dict_s }}" />
</a>
<a class="ibo-navigation-menu--toggler" data-role="ibo-navigation-menu--toggler" data-tooltip-content="{{ 'UI:Layout:NavigationMenu:Toggler:Tooltip'|dict_s }}" data-tooltip-placement="right" data-tooltip-distance-offset="20" href="#">
<span class="ibo-navigation-menu--toggler-icon">
@@ -13,12 +13,23 @@
</a>
</div>
<div class="ibo-navigation-menu--middle-part">
{% for aMenuGroup in aNavigationMenu.aMenuGroups %}
{% for aMenuGroup in oNavigationMenu.MenuGroups %}
{{ include('layouts/navigation-menu/menu-group.html.twig', { aMenuGroup: aMenuGroup }) }}
{% endfor %}
</div>
<div class="ibo-navigation-menu--bottom-part">
<div class="ibo-navigation-menu--notifications">
<div class="ibo-navigation-menu--notifications-toggler"></div>
<div class="ibo-navigation-menu--notifications-menu"></div>
</div>
<div class="ibo-navigation-menu--user-info">
<div class="ibo-navigation-menu--user-picture"></div>
<div class="ibo-navigation-menu--user-welcome-message"></div>
<div class="ibo-navigation-menu--user-organization"></div>
<div class="ibo-navigation-menu--user-menu">
{{ render_block(oNavigationMenu.UserMenu) }}
</div>
</div>
</div>
</div>
<div class="ibo-navigation-menu--drawer" data-role="ibo-navigation-menu--drawer">
@@ -29,7 +40,7 @@
<span class="ibo-navigation-menu--menu-filter-hotkey">{{ 'UI:Layout:NavigationMenu:MenuFilter:Input:Hotkey'|dict_s }}</span>
</div>
<div class="ibo-navigation-menu--menu-groups">
{% for aMenuGroup in aNavigationMenu.aMenuGroups %}
{% for aMenuGroup in oNavigationMenu.MenuGroups %}
{{ include('layouts/navigation-menu/menu-nodes.html.twig', { aMenuGroup: aMenuGroup }) }}
{% endfor %}
</div>

View File

@@ -0,0 +1,6 @@
// TODO: We need to find a clean way to launch this script only once the JS scripts are loaded
document.addEventListener("DOMContentLoaded", function(){
setTimeout(function(){
$('#{{ oNavigationMenu.Id }}').navigation_menu();
}, 500);
});

View File

@@ -1,7 +1,13 @@
<nav id="{{ aTopBar.sId }}" class="ibo-top-bar">
<nav id="{{ oTopBar.Id }}" class="ibo-top-bar">
<div class="ibo-top-bar--quick-actions">
{{ include('components/quick-create/layout.html.twig', { aQuickCreate: aTopBar.aComponents.aQuickCreate }) }}
{{ include('components/global-search/layout.html.twig', { aGlobalSearch: aTopBar.aComponents.aGlobalSearch }) }}
{% if oTopBar.HasQuickCreate %}
{{ render_block(oTopBar.QuickCreate) }}
{% endif %}
{% if oTopBar.HasGlobalSearch %}
{{ render_block(oTopBar.GlobalSearch) }}
{% endif %}
</div>
{{ include('components/breadcrumbs/layout.html.twig', { aBreadCrumbs: aTopBar.aComponents.aBreadCrumbs }) }}
{% if oTopBar.HasBreadcrumbs %}
{{ render_block(oTopBar.Breadcrumbs) }}
{% endif %}
</nav>

View File

@@ -33,9 +33,9 @@
{% endblock %}
</head>
<body data-gui-type="backoffice">
{{ include('layouts/navigation-menu/layout.html.twig', { aNavigationMenu: aLayouts.aNavigationMenu }) }}
{{ render_block(aLayouts.oNavigationMenu) }}
<div id="ibo-page-container">
{{ include('layouts/top-bar/layout.html.twig', { aTopBar: aLayouts.aTopBar }) }}
{{ render_block(aLayouts.oTopBar) }}
<main id="ibo-page-content">
{{ aPage.aSanitizedContent|raw }}
</main>