N°2847 - Tab Management - iTop Pages refactoring - introduction of UIContentBlock as base block

This commit is contained in:
Eric
2020-09-16 12:00:48 +02:00
parent 163c1ebc91
commit 410a637598
46 changed files with 1245 additions and 737 deletions

View File

@@ -2353,4 +2353,9 @@ class utils
{
return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' ')));
}
public static function FilterXSS($sHTML)
{
return str_ireplace('<script', '&lt;script', $sHTML);
}
}

View File

@@ -0,0 +1,17 @@
/*!
* 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
*/

View File

@@ -25,3 +25,7 @@
@import "popover-menu/popover-menu";
@import "popover-menu/popover-menu-item";
@import "newsroom-menu";
@import "tabcontainer";
@import "tab";
@import "ajaxtab";

View File

@@ -0,0 +1,17 @@
/*!
* 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
*/

View File

@@ -0,0 +1,17 @@
/*!
* 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
*/

View File

@@ -0,0 +1,9 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('EN US', 'English', 'English', [
'UIBlock:Error:CannotGetBlocks' => 'Could not retrieve blocks from content area "%1$s" as it does seem to exists for page content "%2$s"',
]);

View File

@@ -0,0 +1,9 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('EN US', 'English', 'English', [
'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)',
]);

View File

@@ -0,0 +1,9 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('EN US', 'English', 'English', [
'UIBlock:Error:AddBlockForbidden' => 'Cannot add block to %1$s',
]);

View File

@@ -0,0 +1,8 @@
# Deprecated in 2.8.0
* TabManager::GetCurrentTabLength()
* TabManager::TruncateTab()
* TabManager::SelectTab()
* TabManager::RenderIntoContent()
* iTopWebPage::SelectTab()

View File

@@ -192,9 +192,15 @@ return array(
'Combodo\\iTop\\Application\\UI\\Layout\\PageContent\\PageContent' => $baseDir . '/sources/application/UI/Layout/PageContent/PageContent.php',
'Combodo\\iTop\\Application\\UI\\Layout\\PageContent\\PageContentFactory' => $baseDir . '/sources/application/UI/Layout/PageContent/PageContentFactory.php',
'Combodo\\iTop\\Application\\UI\\Layout\\PageContent\\PageContentWithSideContent' => $baseDir . '/sources/application/UI/Layout/PageContent/PageContentWithSideContent.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TabContainer\\TabContainer' => $baseDir . '/sources/application/UI/Layout/TabContainer/TabContainer.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TabContainer\\Tab\\AjaxTab' => $baseDir . '/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TabContainer\\Tab\\Tab' => $baseDir . '/sources/application/UI/Layout/TabContainer/Tab/Tab.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\\Layout\\UIContentBlock' => $baseDir . '/sources/application/UI/Layout/UIContentBlock.php',
'Combodo\\iTop\\Application\\UI\\Layout\\iUIContentBlock' => $baseDir . '/sources/application/UI/Layout/iUIContentBlock.php',
'Combodo\\iTop\\Application\\UI\\UIBlock' => $baseDir . '/sources/application/UI/UIBlock.php',
'Combodo\\iTop\\Application\\UI\\UIException' => $baseDir . '/sources/application/UI/UIException.php',
'Combodo\\iTop\\Application\\UI\\iUIBlock' => $baseDir . '/sources/application/UI/iUIBlock.php',
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
@@ -2187,7 +2193,6 @@ return array(
'Twig_Util_DeprecationCollector' => $vendorDir . '/twig/twig/lib/Twig/Util/DeprecationCollector.php',
'Twig_Util_TemplateDirIterator' => $vendorDir . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php',
'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
'UIBlockManager' => $baseDir . '/sources/application/WebPage/UIBlockManager.php',
'UIExtKeyWidget' => $baseDir . '/application/ui.extkeywidget.class.inc.php',
'UIHTMLEditorWidget' => $baseDir . '/application/ui.htmleditorwidget.class.inc.php',
'UILinksWidget' => $baseDir . '/application/ui.linkswidget.class.inc.php',

View File

@@ -422,9 +422,15 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Combodo\\iTop\\Application\\UI\\Layout\\PageContent\\PageContent' => __DIR__ . '/../..' . '/sources/application/UI/Layout/PageContent/PageContent.php',
'Combodo\\iTop\\Application\\UI\\Layout\\PageContent\\PageContentFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/PageContent/PageContentFactory.php',
'Combodo\\iTop\\Application\\UI\\Layout\\PageContent\\PageContentWithSideContent' => __DIR__ . '/../..' . '/sources/application/UI/Layout/PageContent/PageContentWithSideContent.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TabContainer\\TabContainer' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TabContainer/TabContainer.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TabContainer\\Tab\\AjaxTab' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php',
'Combodo\\iTop\\Application\\UI\\Layout\\TabContainer\\Tab\\Tab' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TabContainer/Tab/Tab.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\\Layout\\UIContentBlock' => __DIR__ . '/../..' . '/sources/application/UI/Layout/UIContentBlock.php',
'Combodo\\iTop\\Application\\UI\\Layout\\iUIContentBlock' => __DIR__ . '/../..' . '/sources/application/UI/Layout/iUIContentBlock.php',
'Combodo\\iTop\\Application\\UI\\UIBlock' => __DIR__ . '/../..' . '/sources/application/UI/UIBlock.php',
'Combodo\\iTop\\Application\\UI\\UIException' => __DIR__ . '/../..' . '/sources/application/UI/UIException.php',
'Combodo\\iTop\\Application\\UI\\iUIBlock' => __DIR__ . '/../..' . '/sources/application/UI/iUIBlock.php',
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
@@ -2417,7 +2423,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Twig_Util_DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/DeprecationCollector.php',
'Twig_Util_TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php',
'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
'UIBlockManager' => __DIR__ . '/../..' . '/sources/application/WebPage/UIBlockManager.php',
'UIExtKeyWidget' => __DIR__ . '/../..' . '/application/ui.extkeywidget.class.inc.php',
'UIHTMLEditorWidget' => __DIR__ . '/../..' . '/application/ui.htmleditorwidget.class.inc.php',
'UILinksWidget' => __DIR__ . '/../..' . '/application/ui.linkswidget.class.inc.php',

View File

@@ -35,14 +35,13 @@ try
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
$oPage = new ajax_page("");
$oPage = new AjaxPage("");
$oPage->no_cache();
$operation = utils::ReadParam('operation', '');
$sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class');
switch($operation)
{
switch ($operation) {
case 'download_document':
LoginWebPage::DoLoginEx('backoffice', false);
$id = utils::ReadParam('id', '');

View File

@@ -21,6 +21,7 @@ namespace Combodo\iTop\Renderer;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\iUIBlock;
use utils;
/**
* Class BlockRenderer
@@ -220,6 +221,8 @@ class BlockRenderer
HTML;
}
// TODO 2.8.0
//$sOutput .= utils::FilterXSS($this->RenderHtml());
$sOutput .= $this->RenderHtml();
// JS last so all markup is build and ready
@@ -269,4 +272,4 @@ HTML;
return array_merge(['oUIBlock' => $this->oBlock], $this->aContextParams);
}
}
}

View File

@@ -20,8 +20,7 @@
namespace Combodo\iTop\Application\UI\Component\Panel;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\UIBlock;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
/**
* Class Panel
@@ -30,7 +29,7 @@ use Combodo\iTop\Application\UI\UIBlock;
* @package Combodo\iTop\Application\UI\Component\Panel
* @since 2.8.0
*/
class Panel extends UIBlock
class Panel extends UIContentBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-panel';
@@ -119,63 +118,6 @@ class Panel extends UIBlock
return $this;
}
/**
* @inheritDoc
*/
public function GetSubBlocks()
{
return $this->aSubBlocks;
}
/**
* Set all sub blocks at once, replacing all existing ones
*
* @param \Combodo\iTop\Application\UI\iUIBlock[] $aSubBlocks
*
* @return $this
*/
public function SetSubBlocks(array $aSubBlocks)
{
foreach ($aSubBlocks as $oSubBlock)
{
$this->AddSubBlock($oSubBlock);
}
return $this;
}
/**
* Add $oSubBlock, replacing any block with the same ID
*
* @param \Combodo\iTop\Application\UI\iUIBlock $oSubBlock
*
* @return $this
*/
public function AddSubBlock(iUIBlock $oSubBlock)
{
$this->aSubBlocks[$oSubBlock->GetId()] = $oSubBlock;
return $this;
}
/**
* Remove the sub block identified by $sId.
* Note that if no sub block matches the ID, it proceeds silently.
*
* @param string $sId ID of the sub block to remove
*
* @return $this
*/
public function RemoveSubBlock(string $sId)
{
if (array_key_exists($sId, $this->aSubBlocks))
{
unset($this->aSubBlocks[$sId]);
}
return $this;
}
/**
* @return string
*/
@@ -195,4 +137,4 @@ class Panel extends UIBlock
return $this;
}
}
}

View File

@@ -20,9 +20,13 @@
namespace Combodo\iTop\Application\UI\Layout\PageContent;
use Combodo\iTop\Application\UI\Component\Html\Html;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\UIBlock;
use Exception;
use Combodo\iTop\Application\UI\UIException;
use Dict;
/**
* Class PageContent
@@ -32,7 +36,7 @@ use Exception;
* @internal
* @since 2.8.0
*/
class PageContent extends UIBlock
class PageContent extends UIBlock implements iUIContentBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-page-content';
@@ -41,8 +45,9 @@ class PageContent extends UIBlock
/** @var string ENUM_CONTENT_AREA_MAIN The main content area */
public const ENUM_CONTENT_AREA_MAIN = 'main';
/** @var \Combodo\iTop\Application\UI\iUIBlock[][] $aContentAreasBlocks Blocks for the different content parts of the layout */
/** @var iUIContentBlock[] $aContentAreasBlocks Blocks for the different content parts of the layout */
protected $aContentAreasBlocks;
/** @var string $sExtraHtmlContent HTML content that do not come from blocks and will be output as-is by the component */
protected $sExtraHtmlContent;
@@ -58,102 +63,6 @@ class PageContent extends UIBlock
$this->SetMainBlocks([]);
}
/**
* Set all block for a content area at once, replacing all existing ones.
*
* @param string $sAreaId
* @param \Combodo\iTop\Application\UI\iUIBlock[] $aBlocks
*
* @return $this
*/
protected function SetContentAreaBlocks(string $sAreaId, array $aBlocks)
{
$this->aContentAreasBlocks[$sAreaId] = $aBlocks;
return $this;
}
/**
* Return all blocks from the $sAreaId content area
*
* @param string $sAreaId
*
* @return \Combodo\iTop\Application\UI\iUIBlock[]
* @throws \Exception
*/
protected function GetContentAreaBlocks(string $sAreaId)
{
if (!array_key_exists($sAreaId, $this->aContentAreasBlocks))
{
throw new Exception('Could not retrieve blocks from content area "'.$sAreaId.'" as it does seem to exists for page content "'.$this->GetId().'"');
}
return $this->aContentAreasBlocks[$sAreaId];
}
/**
* Return true if the $sAreaId content area exists
*
* @param string $sAreaId
*
* @return bool
*/
protected function IsExistingContentArea(string $sAreaId)
{
return isset($this->aContentAreasBlocks[$sAreaId]);
}
/**
* Add $oBlock to the $sAreaId content area.
* Note that if the area doesn't exist yet, it is created. Also if a block with the same ID already exists, it will be replaced.
*
* @param string $sAreaId
* @param \Combodo\iTop\Application\UI\iUIBlock $oBlock
*
* @return $this
*/
protected function AddBlockToContentArea(string $sAreaId, iUIBlock $oBlock)
{
if (!array_key_exists($sAreaId, $this->aContentAreasBlocks))
{
$this->aContentAreasBlocks[$sAreaId] = [];
}
$this->aContentAreasBlocks[$sAreaId][$oBlock->GetId()] = $oBlock;
return $this;
}
/**
* Remove the $sBlockId from the $sAreaId content area.
* Note that if the $sBlockId or the $sAreaId do not exist, it proceeds silently.
*
* @param string $sAreaId
* @param string $sBlockId
*
* @return $this
*/
protected function RemoveBlockFromContentArea(string $sAreaId, string $sBlockId)
{
if (array_key_exists($sAreaId, $this->aContentAreasBlocks) && array_key_exists($sBlockId, $this->aContentAreasBlocks[$sAreaId]))
{
unset($this->aContentAreasBlocks[$sAreaId][$sBlockId]);
}
return $this;
}
/**
* Return the content areas IDs
*
* @see static::ENUM_CONTENT_AREA_MAIN, ...
* @return array
*/
protected function EnumContentAreas()
{
return array_keys($this->aContentAreasBlocks);
}
/**
* Set all main blocks at once.
*
@@ -161,7 +70,7 @@ class PageContent extends UIBlock
*
* @return $this
*/
public function SetMainBlocks(array $aBlocks)
public function SetMainBlocks(array $aBlocks): self
{
$this->SetContentAreaBlocks(static::ENUM_CONTENT_AREA_MAIN, $aBlocks);
@@ -208,6 +117,111 @@ class PageContent extends UIBlock
return $this;
}
/**
* Add $oBlock to the $sAreaId content area.
* Note that if the area doesn't exist yet, it is created. Also if a block with the same ID already exists, it will be replaced.
*
* @param string $sAreaId
* @param \Combodo\iTop\Application\UI\iUIBlock $oBlock
*
* @return $this
*/
protected function AddBlockToContentArea(string $sAreaId, iUIBlock $oBlock): self
{
if (!array_key_exists($sAreaId, $this->aContentAreasBlocks)) {
$this->aContentAreasBlocks[$sAreaId] = new UIContentBlock($sAreaId);
}
$this->aContentAreasBlocks[$sAreaId]->AddSubBlock($oBlock);
return $this;
}
public function AddSubBlock(iUIBlock $oSubBlock): iUIContentBlock
{
$this->AddMainBlock($oSubBlock);
return $this->aContentAreasBlocks[static::ENUM_CONTENT_AREA_MAIN];
}
/**
* Remove the $sBlockId from the $sAreaId content area.
* Note that if the $sBlockId or the $sAreaId do not exist, it proceeds silently.
*
* @param string $sAreaId
* @param string $sBlockId
*
* @return $this
*/
protected function RemoveBlockFromContentArea(string $sAreaId, string $sBlockId)
{
if (array_key_exists($sAreaId, $this->aContentAreasBlocks)) {
$this->aContentAreasBlocks[$sAreaId]->RemoveSubBlock($sBlockId);
}
return $this;
}
/**
* Set all block for a content area at once, replacing all existing ones.
*
* @param string $sAreaId
* @param \Combodo\iTop\Application\UI\iUIBlock[] $aBlocks
*
* @return $this
*/
protected function SetContentAreaBlocks(string $sAreaId, array $aBlocks): self
{
if (!isset($this->aContentAreasBlocks[$sAreaId])) {
$this->aContentAreasBlocks[$sAreaId] = new UIContentBlock($sAreaId);
}
$this->aContentAreasBlocks[$sAreaId]->SetSubBlocks($aBlocks);
return $this;
}
/**
* Return all blocks from the $sAreaId content area
*
* @param string $sAreaId
*
* @return \Combodo\iTop\Application\UI\iUIBlock[]
* @throws \Combodo\iTop\Application\UI\UIException
*/
protected function GetContentAreaBlocks(string $sAreaId): array
{
if (!array_key_exists($sAreaId, $this->aContentAreasBlocks)) {
throw new UIException($this, Dict::Format('UIBlock:Error:CannotGetBlocks', $sAreaId, $this->GetId()));
}
return $this->aContentAreasBlocks[$sAreaId]->GetSubBlocks();
}
/**
* Return true if the $sAreaId content area exists
*
* @param string $sAreaId
*
* @return bool
*/
protected function IsExistingContentArea(string $sAreaId)
{
return isset($this->aContentAreasBlocks[$sAreaId]);
}
/**
* Return the content areas IDs
*
* @return array
* @see static::ENUM_CONTENT_AREA_MAIN, ...
*/
protected function EnumContentAreas()
{
return array_keys($this->aContentAreasBlocks);
}
/**
* Set the extra HTML content
*
@@ -215,18 +229,19 @@ class PageContent extends UIBlock
*
* @return $this
*/
public function SetExtraHtmlContent(string $sExtraHtmlContent)
public function SetExtraHtmlContent(string $sExtraHtmlContent): self
{
$this->sExtraHtmlContent = $sExtraHtmlContent;
return $this;
}
public function AddExtraHtmlContent(string $sExtraHtmlContent):iUIBlock
public function AddHtml(string $sHtml): iUIBlock
{
$this->sExtraHtmlContent .= $sExtraHtmlContent;
$oBlock = new Html($sHtml);
$this->AddMainBlock($oBlock);
return $this;
return $oBlock;
}
/**
@@ -239,21 +254,30 @@ class PageContent extends UIBlock
return $this->sExtraHtmlContent;
}
/**
* @inheritDoc
* @throws \Exception
*/
public function GetSubBlocks()
public function GetSubBlocks(): array
{
$aSubBlocks = [];
foreach($this->EnumContentAreas() as $sAreaId)
{
foreach($this->GetContentAreaBlocks($sAreaId) as $oBlock)
{
$aSubBlocks[$oBlock->GetId()] = $oBlock;
}
}
return $this->GetMainBlocks();
}
return $aSubBlocks;
public function GetSubBlock(string $sId): ?iUIBlock
{
return $this->aContentAreasBlocks[static::ENUM_CONTENT_AREA_MAIN]->GetSubBlock($sId);
}
public function SetSubBlocks(array $aSubBlocks): iUIContentBlock
{
$this->SetMainBlocks($aSubBlocks);
return $this;
}
public function RemoveSubBlock(string $sId): iUIContentBlock
{
$this->RemoveMainBlock($sId);
return $this;
}
public function HasSubBlock(string $sId): bool
{
return $this->aContentAreasBlocks[static::ENUM_CONTENT_AREA_MAIN]->HasSubBlock($sId);
}
}

View File

@@ -0,0 +1,118 @@
<?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\TabContainer\Tab;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
use Combodo\iTop\Application\UI\UIException;
use Dict;
use TabManager;
/**
* Class AjaxTab
*
* @package Combodo\iTop\Application\UI\Layout\TabContainer\Tab
*/
class AjaxTab extends Tab
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-ajaxtab';
public const HTML_TEMPLATE_REL_PATH = 'layouts/tabcontainer/ajaxtab/layout';
public const JS_TEMPLATE_REL_PATH = 'layouts/tabcontainer/ajaxtab/layout';
private $sURL;
private $bCache;
public function GetType(): string
{
return TabManager::ENUM_TAB_TYPE_AJAX;
}
/**
* @param string $sHtml
*
* @return \Combodo\iTop\Application\UI\iUIBlock
* @throws \Combodo\iTop\Application\UI\UIException
*/
public function AddHtml(string $sHtml): iUIBlock
{
throw new UIException($this, Dict::Format('UIBlock:Error:AddBlockForbidden', $this->GetId()));
}
/**
* @param \Combodo\iTop\Application\UI\iUIBlock $oSubBlock
*
* @return iUIContentBlock
* @throws \Combodo\iTop\Application\UI\UIException
*/
public function AddSubBlock(iUIBlock $oSubBlock): iUIContentBlock
{
throw new UIException($this, Dict::Format('UIBlock:Error:AddBlockForbidden', $this->GetId()));
}
/**
* @return array|\Combodo\iTop\Application\UI\iUIBlock[]
*/
public function GetSubBlocks(): array
{
return [];
}
/**
* @param mixed $sURL
*
* @return AjaxTab
*/
public function SetURL(string $sURL): self
{
$this->sURL = $sURL;
return $this;
}
/**
* @param mixed $bCache
*
* @return AjaxTab
*/
public function SetCache(string $bCache): self
{
$this->bCache = $bCache;
return $this;
}
/**
* @return mixed
*/
public function GetURL()
{
return $this->sURL;
}
/**
* @return mixed
*/
public function GetCache()
{
return $this->bCache ? 'true' : 'false';
}
}

View File

@@ -0,0 +1,55 @@
<?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\TabContainer\Tab;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
use TabManager;
/**
* Class Tab
*
* @package Combodo\iTop\Application\UI\Layout\TabContainer\Tab
*/
class Tab extends UIContentBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-tab';
public const HTML_TEMPLATE_REL_PATH = 'layouts/tabcontainer/tab/layout';
public const JS_TEMPLATE_REL_PATH = 'layouts/tabcontainer/tab/layout';
protected $sTitle;
public function __construct(string $sTabCode, string $sTitle)
{
parent::__construct($sTabCode);
$this->sTitle = $sTitle;
}
public function GetType(): string
{
return TabManager::ENUM_TAB_TYPE_HTML;
}
public function GetTitle(): string
{
return $this->sTitle;
}
}

View File

@@ -0,0 +1,125 @@
<?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\TabContainer;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
use Combodo\iTop\Application\UI\Layout\TabContainer\Tab\AjaxTab;
use Combodo\iTop\Application\UI\Layout\TabContainer\Tab\Tab;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\UIException;
use Dict;
/**
* Class TabContainer
*
* @package Combodo\iTop\Application\UI\Layout\TabContainer
*/
class TabContainer extends UIContentBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-tabcontainer';
public const HTML_TEMPLATE_REL_PATH = 'layouts/tabcontainer/layout';
public const JS_TEMPLATE_REL_PATH = 'layouts/tabcontainer/layout';
private $sName;
private $sPrefix;
/**
* TabContainer constructor.
*
* @param $sName
* @param $sPrefix
*/
public function __construct($sName, $sPrefix)
{
parent::__construct("{$sName}".((!empty($sPrefix)) ? "-{$sPrefix}" : ""));
$this->sName = $sName;
$this->sPrefix = $sPrefix;
}
/**
* @param string $sTabCode
*
* @return bool
*/
public function TabExists(string $sTabCode): bool
{
return $this->HasSubBlock($sTabCode);
}
public function GetTab($sTabCode): ?Tab
{
/** @var \Combodo\iTop\Application\UI\Layout\TabContainer\Tab\Tab $oTab */
$oTab = $this->GetSubBlock($sTabCode);
return $oTab;
}
/**
* @param string $sTabCode
* @param string $sTitle
*
* @return \Combodo\iTop\Application\UI\Layout\TabContainer\Tab\Tab
* @throws \Combodo\iTop\Application\UI\UIException
*/
public function AddAjaxTab(string $sTabCode, string $sTitle): Tab
{
$oTab = new AjaxTab($sTabCode, $sTitle);
$this->AddSubBlock($oTab);
return $oTab;
}
/**
* @param string $sTabCode
* @param string $sTitle
*
* @return \Combodo\iTop\Application\UI\Layout\TabContainer\Tab\Tab
* @throws \Combodo\iTop\Application\UI\UIException
*/
public function AddTab(string $sTabCode, string $sTitle): Tab
{
$oTab = new Tab($sTabCode, $sTitle);
$this->AddSubBlock($oTab);
return $oTab;
}
public function RemoveTab(string $sTabCode): self
{
$this->RemoveSubBlock($sTabCode);
return $this;
}
/**
* @param \Combodo\iTop\Application\UI\iUIBlock $oSubBlock
*
* @return iUIContentBlock
* @throws \Combodo\iTop\Application\UI\UIException
*/
public function AddSubBlock(iUIBlock $oSubBlock): iUIContentBlock
{
if (!($oSubBlock instanceof Tab)) {
throw new UIException($this, Dict::Format('UIBlock:Error:AddBlockNotTabForbidden', $oSubBlock->GetId(), $this->GetId()));
}
return parent::AddSubBlock($oSubBlock);
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Application\UI\Layout;
use Combodo\iTop\Application\UI\Component\Html\Html;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\UIBlock;
class UIContentBlock extends UIBlock implements iUIContentBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-contentblock';
public const HTML_TEMPLATE_REL_PATH = 'layouts/contentblock/layout';
public const JS_TEMPLATE_REL_PATH = 'layouts/contentblock/layout';
protected $aSubBlocks;
/**
* UIContentBlock constructor.
*
* @param string|null $sName
*/
public function __construct(string $sName = null)
{
parent::__construct($sName);
$this->aSubBlocks = [];
}
public function AddHtml(string $sHtml): iUIBlock
{
$oBlock = new Html($sHtml);
$this->AddSubBlock($oBlock);
return $oBlock;
}
/**
* @inheritDoc
*/
public function GetSubBlocks(): array
{
return $this->aSubBlocks;
}
/**
* @param string $sId
*
* @return \Combodo\iTop\Application\UI\iUIBlock|null
*/
public function GetSubBlock(string $sId): ?iUIBlock
{
return isset($this->aSubBlocks[$sId]) ? $this->aSubBlocks[$sId] : null;
}
/**
* Set all sub blocks at once, replacing all existing ones
*
* @param \Combodo\iTop\Application\UI\iUIBlock[] $aSubBlocks
*
* @return iUIContentBlock
*/
public function SetSubBlocks(array $aSubBlocks): iUIContentBlock
{
foreach ($aSubBlocks as $oSubBlock) {
$this->AddSubBlock($oSubBlock);
}
return $this;
}
/**
* Add $oSubBlock, replacing any block with the same ID
*
* @param \Combodo\iTop\Application\UI\iUIBlock $oSubBlock
*
* @return iUIContentBlock
*/
public function AddSubBlock(iUIBlock $oSubBlock): iUIContentBlock
{
$this->aSubBlocks[$oSubBlock->GetId()] = $oSubBlock;
return $this;
}
/**
* Remove the sub block identified by $sId.
* Note that if no sub block matches the ID, it proceeds silently.
*
* @param string $sId ID of the sub block to remove
*
* @return iUIContentBlock
*/
public function RemoveSubBlock(string $sId): iUIContentBlock
{
if ($this->HasSubBlock($sId)) {
unset($this->aSubBlocks[$sId]);
}
return $this;
}
public function HasSubBlock(string $sId): bool
{
return array_key_exists($sId, $this->aSubBlocks);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Application\UI\Layout;
use Combodo\iTop\Application\UI\iUIBlock;
interface iUIContentBlock
{
public function AddHtml(string $sHtml): iUIBlock;
/**
* @inheritDoc
*/
public function GetSubBlocks(): array;
/**
* @param string $sId
*
* @return \Combodo\iTop\Application\UI\iUIBlock|null
*/
public function GetSubBlock(string $sId): ?iUIBlock;
/**
* Set all sub blocks at once, replacing all existing ones
*
* @param \Combodo\iTop\Application\UI\iUIBlock[] $aSubBlocks
*
* @return $this
*/
public function SetSubBlocks(array $aSubBlocks): iUIContentBlock;
/**
* Add $oSubBlock, replacing any block with the same ID
*
* @param \Combodo\iTop\Application\UI\iUIBlock $oSubBlock
*
* @return $this
*/
public function AddSubBlock(iUIBlock $oSubBlock): iUIContentBlock;
/**
* Remove the sub block identified by $sId.
* Note that if no sub block matches the ID, it proceeds silently.
*
* @param string $sId ID of the sub block to remove
*
* @return $this
*/
public function RemoveSubBlock(string $sId): iUIContentBlock;
public function HasSubBlock(string $sId): bool;
}

View File

@@ -209,8 +209,7 @@ abstract class UIBlock implements iUIBlock
return $aFiles;
}
public function AddExtraHtmlContent(string $sHTML) :iUIBlock
public function AddHtml(string $sHTML): iUIBlock
{
// By default this does nothing
return $this;

View File

@@ -0,0 +1,20 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Application\UI;
use Exception;
use Throwable;
class UIException extends Exception
{
public function __construct(iUIBlock $oBlock, string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($oBlock->GetId().': '.$message, $code, $previous);
}
}

View File

@@ -115,5 +115,5 @@ interface iUIBlock
*
* @return $this
*/
public function AddExtraHtmlContent(string $sHTML) :iUIBlock;
public function AddHtml(string $sHTML): iUIBlock;
}

View File

@@ -4,6 +4,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Renderer\BlockRenderer;
class AjaxPage extends WebPage implements iTabbedPage
{
@@ -15,6 +18,7 @@ class AjaxPage extends WebPage implements iTabbedPage
protected $m_sReadyScript;
protected $m_oTabs;
private $m_sMenu; // If set, then the menu will be updated
const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/ajaxpage/layout';
/**
* constructor for the web page
@@ -44,7 +48,7 @@ class AjaxPage extends WebPage implements iTabbedPage
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
$this->AddUiBlock($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}
/**
@@ -195,10 +199,10 @@ EOF
}
// Render the blocks
$this->s_content = $this->oUIBlockManager->RenderIntoContent($this->s_content, $this);
//$this->s_content = $this->oUIBlockManager->RenderIntoContent($this->s_content, $this);
// Render the tabs in the page (if any)
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
//$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
// Additional UI widgets to be activated inside the ajax fragment
// Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script)
@@ -211,6 +215,40 @@ EOF
}
$this->outputCollapsibleSectionInit();
$aData = [];
$aData['oLayout'] = $this->oContentLayout;
$aData['aPage'] = [
'sAbsoluteUrlAppRoot' => addslashes(utils::GetAbsoluteUrlAppRoot()),
'sTitle' => $this->s_title,
'aMetadata' => [
'sCharset' => static::PAGES_CHARSET,
'sLang' => $this->GetLanguageForMetadata(),
],
'aCssFiles' => $this->a_linked_stylesheets,
'aCssInline' => $this->a_styles,
'aJsFiles' => $this->a_linked_scripts,
'aJsInlineLive' => $this->a_scripts,
// TODO 2.8.0: TEMP, used while developping, remove it.
'sSanitizedContent' => utils::FilterXSS($this->s_content),
'sDeferredContent' => utils::FilterXSS($this->s_deferred_content),
];
$oTwigEnv = TwigHelper::GetTwigEnvironment(BlockRenderer::TWIG_BASE_PATH, BlockRenderer::TWIG_ADDITIONAL_PATHS);
// Render final TWIG into global HTML
$oKpi = new ExecutionKPI();
$sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $this->GetTemplateRelPath());
$oKpi->ComputeAndReport('TWIG rendering');
// Echo global HTML
$oKpi = new ExecutionKPI();
echo $sHtml;
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
return;
$oKPI = new ExecutionKPI();
$s_captured_output = $this->ob_get_clean_safe();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline')) {
@@ -297,13 +335,14 @@ EOF
* @inheritDoc
* @throws \Exception
*/
public function add($sHtml)
public function add($sHtml): ?iUIBlock
{
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != '')) {
$this->m_oTabs->AddToTab($this->m_oTabs->GetCurrentTabContainer(), $this->m_oTabs->GetCurrentTab(), $sHtml);
} else {
parent::add($sHtml);
return parent::add($sHtml);
}
return null;
}
/**

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use \Combodo\iTop\Application\UI\iUIBlock;
/**
* CLI page
@@ -48,9 +49,10 @@ class CLIPage implements Page
}
}
public function add($sText)
public function add($sText): ?iUIBlock
{
echo $sText;
return null;
}
public function p($sText)

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use \Combodo\iTop\Application\UI\iUIBlock;
/**
* Simple web page with no includes or fancy formatting, useful to generateXML documents
@@ -64,9 +65,10 @@ class CSVPage extends WebPage
{
}
public function add($sText)
public function add($sText): ?iUIBlock
{
$this->s_content .= $sText;
return null;
}
public function p($sText)

View File

@@ -22,30 +22,29 @@
*/
class NiceWebPage extends WebPage
{
const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/nicewebpage/layout';
var $m_aReadyScripts;
var $m_sRootUrl;
public function __construct($s_title, $bPrintable = false)
{
parent::__construct($s_title, $bPrintable);
public function __construct($s_title, $bPrintable = false)
{
parent::__construct($s_title, $bPrintable);
$this->m_aReadyScripts = array();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.min.js');
if(utils::IsDevelopmentEnvironment()) // Needed since many other plugins still rely on oldies like $.browser
if (utils::IsDevelopmentEnvironment()) // Needed since many other plugins still rely on oldies like $.browser
{
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.dev.js');
}
else
{
} else {
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.prod.min.js');
}
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui.custom.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui.custom.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
// table sorting
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.pager.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablehover.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/table-selectable-lines.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/table-selectable-lines.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js');
@@ -249,13 +248,24 @@ EOF
parent::output();
}
/**
* @inheritDoc
* @throws \Exception
* @since 2.7.0
*/
protected function LoadTheme()
{
// TODO 2.8.0: Remove light-grey when development of Full Moon is done.
// TODO 2.8.0: Reuse theming mechanism for Full Moon
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
$this->add_linked_stylesheet($sCssThemeUrl);
$sCssRelPath = utils::GetCSSFromSASS(
'css/backoffice/main.scss',
array(
APPROOT.'css/backoffice/',
)
);
$this->add_saas($sCssRelPath);
}
}

View File

@@ -4,6 +4,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\iUIBlock;
/**
* Generic interface common to CLI and Web pages
@@ -24,7 +26,7 @@ interface Page
*
* @return void
*/
public function add($sText);
public function add($sText): ?iUIBlock;
/**
* Add a paragraph to the body of the page

View File

@@ -4,6 +4,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Layout\TabContainer\Tab\Tab;
use Combodo\iTop\Application\UI\Layout\TabContainer\TabContainer;
/**
* Helper class to implement JQueryUI tabs inside a page
@@ -15,13 +18,16 @@ class TabManager
const DEFAULT_TAB_TYPE = self::ENUM_TAB_TYPE_HTML;
/**
* @var TabContainer[]
*/
protected $m_aTabs;
protected $m_sCurrentTabContainer;
protected $m_sCurrentTab;
public function __construct()
{
$this->m_aTabs = array();
$this->m_aTabs = [];
$this->m_sCurrentTabContainer = '';
$this->m_sCurrentTab = '';
}
@@ -30,13 +36,14 @@ class TabManager
* @param string $sTabContainer
* @param string $sPrefix
*
* @return string
* @return \Combodo\iTop\Application\UI\iUIBlock
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
public function AddTabContainer(string $sTabContainer, $sPrefix = ''): TabContainer
{
$this->m_aTabs[$sTabContainer] = array('prefix' => $sPrefix, 'tabs' => array());
$oTabContainer = new TabContainer($sTabContainer, $sPrefix);
$this->m_aTabs[$sTabContainer] = $oTabContainer;
return "\$Tabs:$sTabContainer\$";
return $oTabContainer;
}
/**
@@ -44,19 +51,18 @@ class TabManager
*
* @throws \Exception
*/
public function AddToCurrentTab($sHtml)
public function AddToCurrentTab(string $sHtml): void
{
$this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml);
}
/**
* @return int
* @deprecated 2.8.0
*/
public function GetCurrentTabLength()
{
$iLength = isset($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html']) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html']) : 0;
return $iLength;
return 0;
}
/**
@@ -67,15 +73,11 @@ class TabManager
* @param integer $iLength The length/offset at which to truncate the tab
*
* @return string The truncated part
* @deprecated 2.8.0
*/
public function TruncateTab($sTabContainer, $sTab, $iLength)
public function TruncateTab(string $sTabContainer, string $sTab, int $iLength)
{
$sResult = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'],
$iLength);
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'] = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'],
0, $iLength);
return $sResult;
return '';
}
/**
@@ -84,9 +86,9 @@ class TabManager
*
* @return bool
*/
public function TabExists($sTabContainer, $sTab)
public function TabExists(string $sTabContainer, string $sTab)
{
return isset($this->m_aTabs[$sTabContainer]['tabs'][$sTab]);
return isset($this->m_aTabs[$sTabContainer]) ? $this->m_aTabs[$sTabContainer]->TabExists($sTab) : false;
}
/**
@@ -97,6 +99,14 @@ class TabManager
return count($this->m_aTabs);
}
private function GetTab(string $sTabContainer, string $sTab): ?Tab
{
if ($this->TabExists($sTabContainer, $sTab)) {
return $this->m_aTabs[$sTabContainer]->GetTab($sTab);
}
return null;
}
/**
* @param string $sTabContainer
* @param string $sTabCode
@@ -106,19 +116,16 @@ class TabManager
* @return string
* @throws \Exception
*/
public function AddToTab($sTabContainer, $sTabCode, $sHtml, $sTabTitle = null)
public function AddToTab(string $sTabContainer, string $sTabCode, string $sHtml, $sTabTitle = null): string
{
if (!$this->TabExists($sTabContainer, $sTabCode)) {
$this->InitTab($sTabContainer, $sTabCode, static::ENUM_TAB_TYPE_HTML, $sTabTitle);
}
// If target tab is not of type 'html', throw an exception
if ($this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]['type'] != static::ENUM_TAB_TYPE_HTML) {
throw new Exception("Cannot add HTML content to the tab '$sTabCode' of type '{$this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]['type']}'");
}
$oTab = $this->GetTab($sTabContainer, $sTabCode);
// Append to the content of the tab
$this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]['html'] .= $sHtml;
$oTab->AddHtml($sHtml);
return ''; // Nothing to add to the page for now
}
@@ -139,16 +146,21 @@ class TabManager
/**
* @param string $sTabCode
*
* @param string|null $sTabTitle
*
* @return string
* @throws \Combodo\iTop\Application\UI\UIException
*/
public function SetCurrentTab($sTabCode = '', $sTabTitle = null)
public function SetCurrentTab(string $sTabCode = '', string $sTabTitle = null): ?string
{
$sPreviousTabCode = $this->m_sCurrentTab;
$this->m_sCurrentTab = $sTabCode;
// Init tab to HTML tab if not existing
if (!$this->TabExists($this->GetCurrentTabContainer(), $sTabCode)) {
$this->InitTab($this->GetCurrentTabContainer(), $sTabCode, static::ENUM_TAB_TYPE_HTML, $sTabTitle);
if ($sTabCode != '') {
// Init tab to HTML tab if not existing
if (!$this->TabExists($this->GetCurrentTabContainer(), $sTabCode)) {
$this->InitTab($this->GetCurrentTabContainer(), $sTabCode, static::ENUM_TAB_TYPE_HTML, $sTabTitle);
}
}
return $sPreviousTabCode;
@@ -166,16 +178,20 @@ class TabManager
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. false will cause
* the tab to be reloaded upon each activation.
*
* @param string|null $sTabTitle
*
* @return string
*
* @throws \Combodo\iTop\Application\UI\UIException
* @since 2.0.3
*/
public function AddAjaxTab($sTabCode, $sUrl, $bCache = true, $sTabTitle = null)
public function AddAjaxTab(string $sTabCode, string $sUrl, bool $bCache = true, string $sTabTitle = null): string
{
// Set the content of the tab
$this->InitTab($this->m_sCurrentTabContainer, $sTabCode, static::ENUM_TAB_TYPE_AJAX, $sTabTitle);
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$sTabCode]['url'] = $sUrl;
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$sTabCode]['cache'] = $bCache;
/** @var \Combodo\iTop\Application\UI\Layout\TabContainer\Tab\AjaxTab $oTab */
$oTab = $this->InitTab($this->m_sCurrentTabContainer, $sTabCode, static::ENUM_TAB_TYPE_AJAX, $sTabTitle);
$oTab->SetURL($sUrl)
->SetCache($bCache);
return ''; // Nothing to add to the page for now
}
@@ -200,14 +216,14 @@ class TabManager
* @param string $sTabCode
* @param string|null $sTabContainer
*/
public function RemoveTab($sTabCode, $sTabContainer = null)
public function RemoveTab(string $sTabCode, string $sTabContainer = null)
{
if ($sTabContainer == null) {
$sTabContainer = $this->m_sCurrentTabContainer;
}
if (isset($this->m_aTabs[$sTabContainer]['tabs'][$sTabCode])) {
if (isset($this->m_aTabs[$sTabContainer]) && $this->m_aTabs[$sTabContainer]->TabExists($sTabCode)) {
// Delete the content of the tab
unset($this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]);
$this->m_aTabs[$sTabContainer]->RemoveTab($sTabCode);
// If we just removed the active tab, let's reset the active tab
if (($this->m_sCurrentTabContainer == $sTabContainer) && ($this->m_sCurrentTab == $sTabCode)) {
@@ -224,16 +240,18 @@ class TabManager
*
* @return mixed The actual name of the tab (as a string) or false if not found
*/
public function FindTab($sPattern, $sTabContainer = null)
public function FindTab(string $sPattern, string $sTabContainer = null)
{
$result = false;
if ($sTabContainer == null) {
$sTabContainer = $this->m_sCurrentTabContainer;
}
foreach ($this->m_aTabs[$sTabContainer]['tabs'] as $sTabCode => $void) {
if (preg_match($sPattern, $sTabCode)) {
$result = $sTabCode;
break;
if (isset($this->m_aTabs[$sTabContainer])) {
foreach ($this->m_aTabs[$sTabContainer]->GetSubBlocks() as $sTabCode => $void) {
if (preg_match($sPattern, $sTabCode)) {
$result = $sTabCode;
break;
}
}
}
@@ -250,26 +268,11 @@ class TabManager
* @param string $sTabCode
*
* @return string
* @deprecated 2.8.0
*/
public function SelectTab($sTabContainer, $sTabCode)
public function SelectTab(string $sTabContainer, string $sTabCode)
{
$container_index = 0;
$tab_index = 0;
foreach ($this->m_aTabs as $sCurrentTabContainerName => $aTabs) {
if ($sTabContainer == $sCurrentTabContainerName) {
foreach ($aTabs['tabs'] as $sCurrentTabLabel => $void) {
if ($sCurrentTabLabel == $sTabCode) {
break;
}
$tab_index++;
}
break;
}
$container_index++;
}
$sSelector = '#tabbedContent_'.$container_index.' > ul';
return "window.setTimeout(\"$('$sSelector').tabs('select', $tab_index);\", 100);"; // Let the time to the tabs widget to initialize
return '';
}
/**
@@ -277,14 +280,15 @@ class TabManager
* @param \WebPage $oPage
*
* @return mixed
* @deprecated 2.8.0
*/
public function RenderIntoContent($sContent, WebPage $oPage)
public function RenderIntoContent(string $sContent, WebPage $oPage)
{
// Render the tabs in the page (if any)
$container_index = 0;
foreach ($this->m_aTabs as $sTabContainerName => $aTabs) {
$sTabs = '';
$sPrefix = $aTabs['prefix'];
$container_index = 0;
if (count($aTabs['tabs']) > 0) {
// Clean tabs
foreach ($aTabs['tabs'] as $sTabCode => $aTabData) {
@@ -384,37 +388,36 @@ EOF
* @param string $sTabType
* @param string|null $sTabTitle
*
* @return \Combodo\iTop\Application\UI\Layout\TabContainer\Tab\Tab
* @throws \Combodo\iTop\Application\UI\UIException
* @since 2.7.0
*/
protected function InitTab($sTabContainer, $sTabCode, $sTabType = self::DEFAULT_TAB_TYPE, $sTabTitle = null)
protected function InitTab(string $sTabContainer, string $sTabCode, string $sTabType = self::DEFAULT_TAB_TYPE, string $sTabTitle = null): Tab
{
$oTab = null;
if (!$this->TabExists($sTabContainer, $sTabCode)) {
// Container
if (!array_key_exists($sTabContainer, $this->m_aTabs)) {
$this->m_aTabs[$sTabContainer] = array(
'prefix' => '',
'tabs' => array(),
);
if (!isset($this->m_aTabs[$sTabContainer])) {
$oTabContainer = $this->AddTabContainer($sTabContainer);
} else {
$oTabContainer = $this->m_aTabs[$sTabContainer];
}
// Common properties
$this->m_aTabs[$sTabContainer]['tabs'][$sTabCode] = array(
'type' => $sTabType,
'title' => ($sTabTitle !== null) ? Dict::S($sTabTitle) : Dict::S($sTabCode),
);
$sTitle = ($sTabTitle !== null) ? Dict::S($sTabTitle) : Dict::S($sTabCode);
// Specific properties
switch ($sTabType) {
case static::ENUM_TAB_TYPE_AJAX:
$this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]['url'] = null;
$this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]['cache'] = null;
$oTab = $oTabContainer->AddAjaxTab($sTabCode, $sTitle);
break;
case static::ENUM_TAB_TYPE_HTML:
default:
$this->m_aTabs[$sTabContainer]['tabs'][$sTabCode]['html'] = null;
$oTab = $oTabContainer->AddTab($sTabCode, $sTitle);
break;
}
} else {
$oTab = $this->GetTab($sTabContainer, $sTabCode);
}
return $oTab;
}
}

View File

@@ -1,129 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Renderer\BlockRenderer;
class UIBlockManager
{
/** @var iUIBlock[] */
private $aMainBlocks; // Top blocks to render
/** @var iUIBlock[] */
private $aAllBlocks; // All blocks recursively
private $sCurrentBlockId; // Current block ('' for no current block)
public function __construct()
{
$this->aMainBlocks = [];
$this->aAllBlocks = [];
$this->sCurrentBlockId = '';
}
/**
* Add a block to render
*
* @param iUIBlock $oUIBlock
*/
public function AddBlock(iUIBlock $oUIBlock)
{
$sId = $oUIBlock->GetId();
$this->aMainBlocks[$sId] = $oUIBlock;
$this->aAllBlocks[$sId] = $oUIBlock;
$this->sCurrentBlockId = $sId;
$aSubBlocks = $oUIBlock->GetSubBlocks();
$this->aAllBlocks = array_merge($this->aAllBlocks, $aSubBlocks);
}
public function AddHtml(string $sHTML)
{
if ($this->sCurrentBlockId == '') {
return;
}
$this->aAllBlocks[$this->sCurrentBlockId]->AddExtraHtmlContent($sHTML);
}
/**
* Set the current UIBlock to write into
*
* @param string $sId
*/
public function SetCurrentUIBlock(string $sId = '')
{
$this->sCurrentBlockId = $sId;
}
/**
* Indicates if an UIBlock is the current target to write into
*
* @return bool
*/
public function HasCurrentBlock(): bool
{
return isset($this->aAllBlocks[$this->sCurrentBlockId]);
}
/**
* Get the current UIBlock
*
* @return \Combodo\iTop\Application\UI\iUIBlock|null
*/
public function GetCurrentUIBlock(): ?iUIBlock
{
return $this->GetBlock($this->sCurrentBlockId);
}
/**
* Get UIBlock from Id
*
* @param string $sId
*
* @return \Combodo\iTop\Application\UI\iUIBlock|null
*/
public function GetBlock(string $sId): ?iUIBlock
{
if (isset($this->aAllBlocks[$sId])) {
return $this->aAllBlocks[$sId];
}
return null;
}
/**
* Render the blocks into the page and return the HTML to add
*
* @param string $sContent
* @param \WebPage $oPage
*
* @return string
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function RenderIntoContent(string &$sContent, WebPage $oPage): string
{
foreach ($this->aMainBlocks as $oBlock) {
$oBlockRenderer = new BlockRenderer($oBlock);
// Add HTML
$sContent .= $oBlockRenderer->RenderHtml();
// Add inline CSS and JS
$oPage->add_style($oBlockRenderer->RenderCssInline());
$oPage->add_ready_script($oBlockRenderer->RenderJsInline());
// Add external files
foreach ($oBlockRenderer->GetCssFiles() as $sFile) {
$oPage->add_linked_stylesheet($sFile);
}
foreach ($oBlockRenderer->GetJsFiles() as $sFile) {
$oPage->add_linked_script($sFile);
}
}
return $sContent;
}
}

View File

@@ -19,6 +19,7 @@
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
use Combodo\iTop\Renderer\BlockRenderer;
@@ -42,6 +43,8 @@ class WebPage implements Page
* @since 2.7.0 N°2529
*/
const PAGES_CHARSET = 'utf-8';
const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/webpage/layout';
protected $s_title;
protected $s_content;
protected $s_deferred_content;
@@ -64,8 +67,14 @@ class WebPage implements Page
protected $bPrintable;
protected $bHasCollapsibleSection;
protected $bAddJSDict;
/** @var UIBlockManager */
protected $oUIBlockManager;
/** @var \Combodo\iTop\Application\UI\Layout\iUIContentBlock $oContentLayout */
protected $oContentLayout;
protected $sTemplateRelPath;
/**
* @var bool|string|string[]
*/
private $s_OutputFormat;
/**
* WebPage constructor.
@@ -97,7 +106,9 @@ class WebPage implements Page
$this->bHasCollapsibleSection = false;
$this->bPrintable = $bPrintable;
$this->bAddJSDict = true;
$this->oUIBlockManager = new UIBlockManager();
$this->oContentLayout = new UIContentBlock();
$this->SetTemplateRelPath(static::DEFAULT_PAGE_TEMPLATE_REL_PATH);
ob_start(); // Start capturing the output
}
@@ -128,13 +139,9 @@ class WebPage implements Page
/**
* @inheritDoc
*/
public function add($s_html)
public function add($s_html): ?iUIBlock
{
if ($this->oUIBlockManager->HasCurrentBlock()) {
$this->oUIBlockManager->AddHtml($s_html);
} else {
$this->s_content .= $s_html;
}
return $this->oContentLayout->AddHtml($s_html);
}
/**
@@ -303,19 +310,13 @@ class WebPage implements Page
*
* @param \Combodo\iTop\Application\UI\iUIBlock $oBlock
*
* @param \WebPage $iTopWebPage
*
* @return void
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws \Exception
* @return \Combodo\iTop\Application\UI\iUIBlock block added
* @since 2.8.0
*/
public function AddUiBlock(iUIBlock $oBlock, WebPage $iTopWebPage)
public function AddUiBlock(iUIBlock $oBlock): iUIBlock
{
$iTopWebPage->GetUIBlockManager()->AddBlock($oBlock);
$this->oContentLayout->AddSubBlock($oBlock);
return $oBlock;
}
/**
@@ -695,112 +696,84 @@ class WebPage implements Page
*/
public function output()
{
foreach ($this->a_headers as $s_header)
{
header($s_header);
}
$s_captured_output = $this->ob_get_clean_safe();
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
echo "<html>\n";
echo "<head>\n";
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
echo "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\" />";
echo "<title>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</title>\n";
echo $this->get_base_tag();
// Render the blocks
$this->s_content = $this->oUIBlockManager->RenderIntoContent($this->s_content, $this);
// First put stylesheets so they can be loaded before browser interprets JS files, otherwise visual glitch can occur.
foreach ($this->a_linked_stylesheets as $a_stylesheet)
{
if (strpos($a_stylesheet['link'], '?') === false)
{
$s_stylesheet = $a_stylesheet['link']."?t=".utils::GetCacheBusterTimestamp();
}
else
{
$s_stylesheet = $a_stylesheet['link']."&t=".utils::GetCacheBusterTimestamp();
}
if ($a_stylesheet['condition'] != "")
{
echo "<!--[if {$a_stylesheet['condition']}]>\n";
}
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$s_stylesheet}\" />\n";
if ($a_stylesheet['condition'] != "")
{
echo "<![endif]-->\n";
// Send headers
if ($this->GetOutputFormat() === 'html') {
foreach ($this->a_headers as $sHeader) {
header($sHeader);
}
}
// Then inline styles
if (count($this->a_styles) > 0)
{
echo "<style>\n";
foreach ($this->a_styles as $s_style)
{
echo "$s_style\n";
}
echo "</style>\n";
$this->s_content = $this->ob_get_clean_safe();
$aData = [];
$aData['oLayout'] = $this->oContentLayout;
// CSS files
foreach ($this->oContentLayout->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) {
$this->add_linked_stylesheet($sFileAbsUrl);
}
// JS files
foreach ($this->oContentLayout->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) {
$this->add_linked_script($sFileAbsUrl);
}
// Base structure of data to pass to the TWIG template
$aData['aPage'] = [
'sAbsoluteUrlAppRoot' => addslashes(utils::GetAbsoluteUrlAppRoot()),
'sTitle' => $this->s_title,
'aMetadata' => [
'sCharset' => static::PAGES_CHARSET,
'sLang' => $this->GetLanguageForMetadata(),
],
'aCssFiles' => $this->a_linked_stylesheets,
'aCssInline' => $this->a_styles,
'aJsFiles' => $this->a_linked_scripts,
'aJsInlineLive' => $this->a_scripts,
// TODO 2.8.0: TEMP, used while developping, remove it.
'sSanitizedContent' => utils::FilterXSS($this->s_content),
'sDeferredContent' => utils::FilterXSS($this->s_deferred_content),
];
if ($this->a_base['href'] != '') {
$aData['aPage']['aMetadata']['sBaseUrl'] = $this->a_base['href'];
}
if ($this->a_base['target'] != '') {
$aData['aPage']['aMetadata']['sBaseTarget'] = $this->a_base['target'];
}
// Favicon
if (class_exists('MetaModel') && MetaModel::GetConfig())
{
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?t=".utils::GetCacheBusterTimestamp()."\" />\n";
if (class_exists('MetaModel') && MetaModel::GetConfig()) {
$aData['aPage']['sFaviconUrl'] = $this->GetFaviconAbsoluteUrl();
}
// Dict entries for JS
if ($this->bAddJSDict)
{
$this->output_dict_entries();
}
// if ($this->bAddJSDict) {
// $this->output_dict_entries();
// }
// JS files
foreach ($this->a_linked_scripts as $s_script)
{
// Make sure that the URL to the script contains the application's version number
// so that the new script do NOT get reloaded from the cache when the application is upgraded
if (strpos($s_script, '?') === false)
{
$s_script .= "?t=".utils::GetCacheBusterTimestamp();
}
else
{
$s_script .= "&t=".utils::GetCacheBusterTimestamp();
}
echo "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
}
// JS inline scripts
if (count($this->a_scripts) > 0)
{
echo "<script type=\"text/javascript\">\n";
foreach ($this->a_scripts as $s_script)
{
echo "$s_script\n";
}
echo "</script>\n";
}
// if (trim($s_captured_output) != "") {
// echo "<div class=\"raw_output\">".utils::FilterXSS($s_captured_output)."</div>\n";
// }
echo "</head>\n";
echo "<body>\n";
echo self::FilterXSS($this->s_content);
if (trim($s_captured_output) != "")
{
echo "<div class=\"raw_output\">".self::FilterXSS($s_captured_output)."</div>\n";
}
echo '<div id="at_the_end">'.self::FilterXSS($this->s_deferred_content).'</div>';
echo "</body>\n";
echo "</html>\n";
$oTwigEnv = TwigHelper::GetTwigEnvironment(BlockRenderer::TWIG_BASE_PATH, BlockRenderer::TWIG_ADDITIONAL_PATHS);
// Render final TWIG into global HTML
$oKpi = new ExecutionKPI();
$sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $this->GetTemplateRelPath());
$oKpi->ComputeAndReport('TWIG rendering');
if (class_exists('DBSearch'))
{
// Echo global HTML
$oKpi = new ExecutionKPI();
echo $sHtml;
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
if (class_exists('DBSearch')) {
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
if (class_exists('ExecutionKPI')) {
ExecutionKPI::ReportStats();
}
}
@@ -824,31 +797,6 @@ class WebPage implements Page
}
}
/**
* Return the HTML base tag
*
* @return string
*/
protected function get_base_tag()
{
$sTag = '';
if (($this->a_base['href'] != '') || ($this->a_base['target'] != ''))
{
$sTag = '<base ';
if (($this->a_base['href'] != ''))
{
$sTag .= "href =\"{$this->a_base['href']}\" ";
}
if (($this->a_base['target'] != ''))
{
$sTag .= "target =\"{$this->a_base['target']}\" ";
}
$sTag .= " />\n";
}
return $sTag;
}
/**
* Get an ID (for any kind of HTML tag) that is guaranteed unique in this page
*
@@ -907,11 +855,6 @@ class WebPage implements Page
return $this->iTransactionId;
}
public static function FilterXSS($sHTML)
{
return str_ireplace('<script', '&lt;script', $sHTML);
}
/**
* What is the currently selected output format
*
@@ -1149,11 +1092,55 @@ EOD
}
/**
* @return \UIBlockManager
* Return the language for the page metadata based on the current user
*
* @return string
* @since 2.8.0
*/
public function GetUIBlockManager(): \UIBlockManager
protected function GetLanguageForMetadata()
{
return $this->oUIBlockManager;
$sUserLang = UserRights::GetUserLanguage();
return strtolower(substr($sUserLang, 0, 2));
}
/**
* Return the absolute URL for the favicon
*
* @return string
* @throws \Exception
* @since 2.8.0
*/
protected function GetFaviconAbsoluteUrl()
{
// TODO 2.8.0: Make it a property so it can be changed programmatically
// TODO 2.8.0: How to set both dark/light mode favicons
return utils::GetAbsoluteUrlAppRoot().'images/favicon.ico';
}
/**
* Set the template path to use for the page
*
* @param string $sTemplateRelPath Relative path (from <ITOP>/templates/) to the template path
*
* @return $this
* @since 2.8.0
*/
public function SetTemplateRelPath($sTemplateRelPath)
{
$this->sTemplateRelPath = $sTemplateRelPath;
return $this;
}
/**
* Return the relative path (from <ITOP>/templates/) to the page template
*
* @return string
* @since 2.8.0
*/
public function GetTemplateRelPath()
{
return $this->sTemplateRelPath;
}
}

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use \Combodo\iTop\Application\UI\iUIBlock;
/**
* Class XMLPage
@@ -68,7 +69,7 @@ class XMLPage extends WebPage
}
}
public function add($sText)
public function add($sText): ?iUIBlock
{
if (!$this->m_bPassThrough)
{
@@ -95,6 +96,7 @@ class XMLPage extends WebPage
$this->m_bHeaderSent = true;
}
}
return null;
}
public function small_p($sText)

View File

@@ -39,14 +39,12 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
/** @var string DEFAULT_BREADCRUMB_ENTRY_ICON_TYPE */
const DEFAULT_BREADCRUMB_ENTRY_ICON_TYPE = self::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_IMAGE;
/** @var string DEFAULT_PAGE_TEMPLATE_REL_PATH The relative path (from <ITOP>/templates/) to the default page template */
/** @var string DEFAULT_PAGE_TEMPLATE_REL_PATH The relative path (from <ITOP>/templates/) to the default page template */
const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/layout';
private $m_aMessages;
private $m_aInitScript = array();
protected $sTemplateRelPath;
/** @var \Combodo\iTop\Application\UI\Layout\PageContent\PageContent $oContentLayout */
protected $oContentLayout;
protected $m_oTabs;
protected $bBreadCrumbEnabled;
protected $sBreadCrumbEntryId;
@@ -72,7 +70,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->m_oTabs = new TabManager();
$this->oCtx = new ContextTag(ContextTag::TAG_CONSOLE);
$this->SetTemplateRelPath(static::DEFAULT_PAGE_TEMPLATE_REL_PATH);
// By default, content layout is empty, only manually added content will be displayed (eg. $this->add(xxx))
$this->SetContentLayout(PageContentFactory::MakeStandardEmpty());
@@ -155,31 +152,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
}
}
/**
* Set the template path to use for the page
*
* @param string $sTemplateRelPath Relative path (from <ITOP>/templates/) to the template path
*
* @return $this
* @since 2.8.0
*/
public function SetTemplateRelPath($sTemplateRelPath)
{
$this->sTemplateRelPath = $sTemplateRelPath;
return $this;
}
/**
* Return the relative path (from <ITOP>/templates/) to the page template
*
* @return string
* @since 2.8.0
*/
public function GetTemplateRelPath()
{
return $this->sTemplateRelPath;
}
/**
*
*/
@@ -663,25 +635,7 @@ JS
);
}
/**
* @inheritDoc
* @throws \Exception
*/
protected function LoadTheme()
{
// TODO 2.8.0: Remove light-grey when development of Full Moon is done.
// TODO 2.8.0: Reuse theming mechanism for Full Moon
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
$this->add_linked_stylesheet($sCssThemeUrl);
$sCssRelPath = utils::GetCSSFromSASS(
'css/backoffice/main.scss',
array(
APPROOT.'css/backoffice/',
)
);
$this->add_saas($sCssRelPath);
}
/**
* @see static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_IMAGE, static::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES
@@ -784,32 +738,6 @@ JS
return $sHtml;
}
/**
* Return the language for the page metadata based on the current user
*
* @return string
* @since 2.8.0
*/
protected function GetLanguageForMetadata()
{
$sUserLang = UserRights::GetUserLanguage();
return strtolower(substr($sUserLang, 0 ,2));
}
/**
* Return the absolute URL for the favicon
*
* @return string
* @throws \Exception
* @since 2.8.0
*/
protected function GetFaviconAbsoluteUrl()
{
// TODO 2.8.0: Make it a property so it can be changed programmatically
// TODO 2.8.0: How to set both dark/light mode favicons
return utils::GetAbsoluteUrlAppRoot().'images/favicon.ico';
}
/**
* Return the navigation menu layout (id, menu groups, ...)
@@ -855,6 +783,7 @@ JS
public function SetContentLayout(PageContent $oLayout)
{
$this->oContentLayout = $oLayout;
return $this;
}
@@ -867,7 +796,9 @@ JS
*/
protected function GetContentLayout()
{
return $this->oContentLayout;
/** @var PageContent $oPageContent */
$oPageContent = $this->oContentLayout;
return $oPageContent;
}
/**
@@ -1046,14 +977,7 @@ EOF;
// Prepare internal parts (js files, css files, js snippets, css snippets, ...)
// - Generate necessary dict. files
$this->output_dict_entries();
// TODO 2.8.0: Check if we can keep this as is
// Render the blocks
$this->s_content = $this->oUIBlockManager->RenderIntoContent($this->s_content, $this);
// Render the tabs in the page (if any)
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
$this->GetContentLayout()->SetExtraHtmlContent(self::FilterXSS($this->s_content));
$this->GetContentLayout()->SetExtraHtmlContent(utils::FilterXSS($this->s_content));
// Base structure of data to pass to the TWIG template
$aData['aPage'] = [
@@ -1068,11 +992,14 @@ EOF;
// Base tag
// Note: We might consider to put the app_root_url parameter here, but that would need a BIG rework on iTop AND the extensions to replace all the "../images|js|css/xxx.yyy"...
if(!empty($this->a_base['href']))
{
if (!empty($this->a_base['href'])) {
$aData['aPage']['aMetadata']['sBaseUrl'] = $this->a_base['href'];
}
if ($this->a_base['target'] != '') {
$aData['aPage']['aMetadata']['sBaseTarget'] = $this->a_base['target'];
}
// Layouts
$aData['aLayouts'] = [
'sBanner' => $this->RenderBannerHtml(),
@@ -1121,8 +1048,8 @@ EOF;
'aJsInlineOnDomReady' => $this->m_aReadyScripts,
'aJsInlineLive' => $this->a_scripts,
// TODO 2.8.0: TEMP, used while developping, remove it.
'sSanitizedContent' => self::FilterXSS($this->s_content),
'sDeferredContent' => self::FilterXSS($this->s_deferred_content),
'sSanitizedContent' => utils::FilterXSS($this->s_content),
'sDeferredContent' => utils::FilterXSS($this->s_deferred_content),
]
);
@@ -1285,28 +1212,22 @@ EOF;
// $sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
// $sDefaultPlaceHolder = Dict::S("UI:YourSearch");
if ($this->IsPrintableVersion())
{
if ($this->IsPrintableVersion()) {
$sHtml .= ' <!-- Beginning of page content -->';
$sHtml .= self::FilterXSS($this->s_content);
$sHtml .= utils::FilterXSS($this->s_content);
$sHtml .= ' <!-- End of page content -->';
}
elseif ($this->GetOutputFormat() == 'html')
{
} elseif ($this->GetOutputFormat() == 'html') {
// Add the captured output
if (trim($s_captured_output) != "")
{
$sHtml .= "<div id=\"rawOutput\" title=\"Debug Output\"><div style=\"height:500px; overflow-y:auto;\">".self::FilterXSS($s_captured_output)."</div></div>\n";
if (trim($s_captured_output) != "") {
$sHtml .= "<div id=\"rawOutput\" title=\"Debug Output\"><div style=\"height:500px; overflow-y:auto;\">".utils::FilterXSS($s_captured_output)."</div></div>\n";
}
$sHtml .= "<div id=\"at_the_end\">".self::FilterXSS($this->s_deferred_content)."</div>";
$sHtml .= "<div id=\"at_the_end\">".utils::FilterXSS($this->s_deferred_content)."</div>";
$sHtml .= "<div style=\"display:none\" title=\"ex2\" id=\"ex2\">Please wait...</div>\n"; // jqModal Window
$sHtml .= "<div style=\"display:none\" title=\"dialog\" id=\"ModalDlg\"></div>";
$sHtml .= "<div style=\"display:none\" id=\"ajax_content\"></div>";
}
else
{
$sHtml .= self::FilterXSS($this->s_content);
} else {
$sHtml .= utils::FilterXSS($this->s_content);
}
if ($this->IsPrintableVersion())
@@ -1362,7 +1283,7 @@ EOF;
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
$this->AddUiBlock($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}
/**
@@ -1432,6 +1353,8 @@ EOF;
*
* @param string $sTabContainer
* @param string $sTabCode
*
* @deprecated 2.8.0
*/
public function SelectTab($sTabContainer, $sTabCode)
{
@@ -1442,16 +1365,14 @@ EOF;
* @inheritDoc
* @throws \Exception
*/
public function add($sHtml)
public function add($sHtml): ?iUIBlock
{
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != ''))
{
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != '')) {
$this->m_oTabs->AddToCurrentTab($sHtml);
} else {
return parent::add($sHtml);
}
else
{
parent::add($sHtml);
}
return null;
}
/**

View File

@@ -1 +1,3 @@
<div class="ibo-html">{{ oUIBlock.GetHtml()|raw }}</div>
{% apply spaceless %}
{{ oUIBlock.GetHtml()|raw }}
{% endapply %}

View File

@@ -0,0 +1,9 @@
{% apply spaceless %}
{# <div id="{{ oUIBlock.GetId() }}" class="ibo-content-block"> #}
{% block iboContentBlockContainer %}
{% for oSubBlock in oUIBlock.GetSubBlocks() %}
{{ render_block(oSubBlock, {aPage: aPage}) }}
{% endfor %}
{% endblock %}
{# </div> #}
{% endapply %}

View File

@@ -0,0 +1,25 @@
<div id="{{ oUIBlock.GetId() }}" class="ibo-tab-container">
{% block iboTabContainer %}
<!-- tabs -->
<div id="tabbedContent_{$sPrefix}{$container_index}" class="light">
<ul>
{% for oTab in oUIBlock.GetSubBlocks() %}
{% if oTab.GetType() == 'ajax' %}
<li data-cache="{{ oTab.GetCache() }}"><a href="{{ oTab.GetURL() }}" class="tab" data-tab-id="$sTabCodeForHtml"><span>{{ oTab.GetTitle() }}</span></a></li>
{% elseif oTab.GetType() == 'html' %}
<li><a href="#tab_{{ oTab.GetId() }}" class="tab" data-tab-id="$sTabCodeForHtml"><span>{{ oTab.GetTitle() }}</span></a></li>
{% endif %}
{% endfor %}
</ul>
{% for oTab in oUIBlock.GetSubBlocks() %}
{% if oTab.GetType() == 'html' %}
<div id="tab_{{ oTab.GetId() }}">
{{ render_block(oTab, {aPage: aPage}) }}
</div>
{% endif %}
{% endfor %}
</div>
<!-- end of tabs-->
{% endblock %}
</div>

View File

@@ -0,0 +1,42 @@
// The "tab widgets" to handle.
var tabs = $('div[id^=tabbedContent]');
// Ugly patch for a change in the behavior of jQuery UI:
// Before jQuery UI 1.9, tabs were always considered as "local" (opposed to Ajax)
// when their href was beginning by #. Starting with 1.9, a <base> tag in the page
// is taken into account and causes "local" tabs to be considered as Ajax
// unless their URL is equal to the URL of the page...
if ($('base').length > 0) {
$('div[id^=tabbedContent] > ul > li > a').each(function () {
var sHash = location.hash;
var sCleanLocation = location.href.toString().replace(sHash, '').replace(/#$/, '');
$(this).attr("href", sCleanLocation + $(this).attr("href"));
});
}
if ($.bbq) {
// This selector will be reused when selecting actual tab widget A elements.
var tab_a_selector = 'ul.ui-tabs-nav a';
// Enable tabs on all tab widgets. The `event` property must be overridden so
// that the tabs aren't changed on click, and any custom event name can be
// specified. Note that if you define a callback for the 'select' event, it
// will be executed for the selected tab whenever the hash changes.
tabs.tabs({event: 'change'});
// Define our own click handler for the tabs, overriding the default.
tabs.find(tab_a_selector).click(function () {
var state = {};
// Get the id of this tab widget.
var id = $(this).closest('div[id^=tabbedContent]').attr('id');
// Get the index of this tab.
var idx = $(this).parent().prevAll().length;
// Set the state!
state[id] = idx;
$.bbq.pushState(state);
});
} else {
tabs.tabs();
}

View File

@@ -0,0 +1,7 @@
<div id="{{ oUIBlock.GetId() }}" class="ibo-tab-content-block">
{% block iboContentBlockContainer %}
{% for oSubBlock in oUIBlock.GetSubBlocks() %}
{{ render_block(oSubBlock, {aPage: aPage}) }}
{% endfor %}
{% endblock %}
</div>

View File

@@ -0,0 +1 @@
{{ render_block(oLayout, {aPage: aPage}) }}

View File

@@ -1,38 +1,6 @@
<!DOCTYPE html>
<html lang="{{ aPage.aMetadata.sLang }}">
<head>
<meta charset="{{ aPage.aMetadata.sCharset }}">
{# This block can be used to add your own meta tags by extending the default template #}
{% block iboPageExtraMetas %}
{% endblock %}
{% if aPage.aMetadata.sBaseUrl is defined %}
<base href="{{ aPage.aMetadata.sBaseUrl }}">
{% endif %}
<title>{{ aPage.sTitle }}</title>
<link rel="shortcut icon" href="{{ aPage.sFaviconUrl|add_itop_version }}" />
<link rel="search" type="application/opensearchdescription+xml" title="iTop" href="{{ aPage.sAbsoluteUrlAppRoot }}pages/opensearch.xml.php" />
{% extends "pages/backoffice/nicewebpage/layout.html.twig" %}
{# Stylesheets MUST be loaded before any scripts otherwise we may face problems such as
- Visual glitches
- jQuery scripts spurious problems (like failing on a 'reload') #}
{% block iboPageCssFiles %}
{% for aCssFileData in aPage.aCssFiles %}
{% if aCssFileData['condition'] != '' %}<!--[if {{ aCssFileData['condition'] }}]>{% endif %}
<link type="text/css" href="{{ aCssFileData['link']|add_itop_version }}" rel="stylesheet" />
{% if aCssFileData['condition'] != '' %}<![endif]-->{% endif %}
{% endfor %}
{% endblock %}
{% block iboPageCssInline %}
{# We put each styles in a dedicated style tag to prevent massive failure if 1 style is broken (eg. missing semi-colon, bracket, ...) #}
{% for sCssInline in aPage.aCssInline %}
<style>
{{ sCssInline|raw }}
</style>
{% endfor %}
{% endblock %}
</head>
<body data-gui-type="backoffice">
{% block iboPageBodyHtml %}
{{ render_block(aLayouts.oNavigationMenu, {aPage: aPage}) }}
<div id="ibo-page-container">
<div id="ibo-top-container">
@@ -49,41 +17,10 @@
<div style="display:none" title="dialog" id="ModalDlg"></div>
<div style="display:none" id="ajax_content"></div>
</div>
{% endblock %}
{% block iboPageJsFiles %}
{% for sJsFile in aPage.aJsFiles %}
<script type="text/javascript" src="{{ sJsFile|add_itop_version }}"></script>
{% endfor %}
{% endblock %}
{% block iboPageJsInlineScripts %}
<script type="text/javascript">
{# TODO: How to do this in native JS? #}
$(document).ready(function(){
{% block iboPageJsInlineOnInit %}
{% for sJsInline in aPage.aJsInlineOnInit %}
{{ sJsInline|raw }}
{% endfor %}
{% endblock %}
{% block iboPageJsInlineOnDomReady %}
setTimeout(function(){
{% for sJsInline in aPage.aJsInlineOnDomReady %}
{{ sJsInline|raw }}
{% endfor %}
}, 50);
{% endblock %}
});
</script>
{% block iboPageJsInlineLive %}
{% for sJsInline in aPage.aJsInlineLive %}
{# We put each scripts in a dedicated script tag to prevent massive failure if 1 script is broken (eg. missing semi-colon or non closed multi-line comment) #}
<script type="text/javascript">
{{ sJsInline|raw }}
</script>
{% endfor %}
{% endblock %}
{% endblock %}
</body>
</html>
{% block iboPageJsInlineOnInit %}
{% for sJsInline in aPage.aJsInlineOnInit %}
{{ sJsInline|raw }}
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "pages/backoffice/webpage/layout.html.twig" %}
{% block iboPageJsInlineOnDomReady %}
setTimeout(function () {
{% for sJsInline in aPage.aJsInlineOnDomReady %}
{{ sJsInline|raw }}
{% endfor %}
}, 50);
{% endblock %}

View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="{{ aPage.aMetadata.sLang }}">
<head>
<meta charset="{{ aPage.aMetadata.sCharset }}">
{# This block can be used to add your own meta tags by extending the default template #}
{% block iboPageExtraMetas %}
{% endblock %}
{% if aPage.aMetadata.sBaseUrl is defined or aPage.aMetadata.sBaseTarget is defined %}
<base {% if aPage.aMetadata.sBaseUrl is defined %}href="{{ aPage.aMetadata.sBaseUrl }}"{% endif %} {% if aPage.aMetadata.sBaseTarget is defined %}target="{{ aPage.aMetadata.sBaseTarget }}"{% endif %}>
{% endif %}
<title>{{ aPage.sTitle }}</title>
{% if aPage.sFaviconUrl is defined %}
<link rel="shortcut icon" href="{{ aPage.sFaviconUrl|add_itop_version }}"/>
{% endif %}
<link rel="search" type="application/opensearchdescription+xml" title="iTop" href="{{ aPage.sAbsoluteUrlAppRoot }}pages/opensearch.xml.php"/>
{# Stylesheets MUST be loaded before any scripts otherwise we may face problems such as
- Visual glitches
- jQuery scripts spurious problems (like failing on a 'reload') #}
{% block iboPageCssFiles %}
{% for aCssFileData in aPage.aCssFiles %}
{% if aCssFileData['condition'] != '' %}<!--[if {{ aCssFileData['condition'] }}]>{% endif %}
<link type="text/css" href="{{ aCssFileData['link']|add_itop_version }}" rel="stylesheet" />
{% if aCssFileData['condition'] != '' %}<![endif]-->{% endif %}
{% endfor %}
{% endblock %}
{% block iboPageCssInline %}
{# We put each styles in a dedicated style tag to prevent massive failure if 1 style is broken (eg. missing semi-colon, bracket, ...) #}
{% for sCssInline in aPage.aCssInline %}
<style>
{{ sCssInline|raw }}
</style>
{% endfor %}
{% endblock %}
</head>
<body data-gui-type="backoffice">
{% block iboPageBodyHtml %}
<div id="ibo-page-container">
{{ render_block(oLayout, {aPage: aPage}) }}
{# TODO: Remove this when modal development is done #}
<div id="at_the_end">{{ aPage.sDeferredContent|raw }}</div>
<div style="display:none" title="ex2" id="ex2">Please wait...</div>
<div style="display:none" title="dialog" id="ModalDlg"></div>
<div style="display:none" id="ajax_content"></div>
</div>
{% endblock %}
{% block iboPageJsFiles %}
{% for sJsFile in aPage.aJsFiles %}
<script type="text/javascript" src="{{ sJsFile|add_itop_version }}"></script>
{% endfor %}
{% endblock %}
{% block iboPageJsInlineScripts %}
<script type="text/javascript">
{# TODO: How to do this in native JS? #}
$(document).ready(function () {
{% block iboPageJsInlineOnInit %}
{% endblock %}
{% block iboPageJsInlineOnDomReady %}
{% endblock %}
});
</script>
{% block iboPageJsInlineLive %}
{% for sJsInline in aPage.aJsInlineLive %}
{# We put each scripts in a dedicated script tag to prevent massive failure if 1 script is broken (eg. missing semi-colon or non closed multi-line comment) #}
<script type="text/javascript">
{{ sJsInline|raw }}
</script>
{% endfor %}
{% endblock %}
{% endblock %}
</body>
</html>