diff --git a/application/utils.inc.php b/application/utils.inc.php index 3e97c5ab8..7e29569c1 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -2353,4 +2353,9 @@ class utils { return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' '))); } + + public static function FilterXSS($sHTML) + { + return str_ireplace(' 'Could not retrieve blocks from content area "%1$s" as it does seem to exists for page content "%2$s"', +]); diff --git a/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php new file mode 100644 index 000000000..e8b661324 --- /dev/null +++ b/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php @@ -0,0 +1,9 @@ + 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)', +]); diff --git a/dictionaries/ui/layouts/en.dictionary.itop.ui-content-block.php b/dictionaries/ui/layouts/en.dictionary.itop.ui-content-block.php new file mode 100644 index 000000000..5b058db8f --- /dev/null +++ b/dictionaries/ui/layouts/en.dictionary.itop.ui-content-block.php @@ -0,0 +1,9 @@ + 'Cannot add block to %1$s', +]); diff --git a/documentation/deprecations.md b/documentation/deprecations.md new file mode 100644 index 000000000..741500ee5 --- /dev/null +++ b/documentation/deprecations.md @@ -0,0 +1,8 @@ +# Deprecated in 2.8.0 + + * TabManager::GetCurrentTabLength() + * TabManager::TruncateTab() + * TabManager::SelectTab() + * TabManager::RenderIntoContent() + + * iTopWebPage::SelectTab() diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index e8dd2f64d..86fc668f2 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -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', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 0ba942285..8be1f22d4 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -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', diff --git a/pages/ajax.document.php b/pages/ajax.document.php index b7f1a0d70..c2e563e81 100644 --- a/pages/ajax.document.php +++ b/pages/ajax.document.php @@ -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', ''); diff --git a/sources/Renderer/BlockRenderer.php b/sources/Renderer/BlockRenderer.php index 6e1c4fedc..9c2d206b6 100644 --- a/sources/Renderer/BlockRenderer.php +++ b/sources/Renderer/BlockRenderer.php @@ -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); } -} \ No newline at end of file +} diff --git a/sources/application/UI/Component/Panel/Panel.php b/sources/application/UI/Component/Panel/Panel.php index d8b19e35a..7f6c1645d 100644 --- a/sources/application/UI/Component/Panel/Panel.php +++ b/sources/application/UI/Component/Panel/Panel.php @@ -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; } -} \ No newline at end of file +} diff --git a/sources/application/UI/Layout/PageContent/PageContent.php b/sources/application/UI/Layout/PageContent/PageContent.php index 2fa649e97..fe0a1a193 100644 --- a/sources/application/UI/Layout/PageContent/PageContent.php +++ b/sources/application/UI/Layout/PageContent/PageContent.php @@ -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); } } diff --git a/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php b/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php new file mode 100644 index 000000000..319de6f6b --- /dev/null +++ b/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php @@ -0,0 +1,118 @@ +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'; + } + + +} diff --git a/sources/application/UI/Layout/TabContainer/Tab/Tab.php b/sources/application/UI/Layout/TabContainer/Tab/Tab.php new file mode 100644 index 000000000..45a8a74ce --- /dev/null +++ b/sources/application/UI/Layout/TabContainer/Tab/Tab.php @@ -0,0 +1,55 @@ +sTitle = $sTitle; + } + + public function GetType(): string + { + return TabManager::ENUM_TAB_TYPE_HTML; + } + + public function GetTitle(): string + { + return $this->sTitle; + } +} diff --git a/sources/application/UI/Layout/TabContainer/TabContainer.php b/sources/application/UI/Layout/TabContainer/TabContainer.php new file mode 100644 index 000000000..32e59f147 --- /dev/null +++ b/sources/application/UI/Layout/TabContainer/TabContainer.php @@ -0,0 +1,125 @@ +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); + } +} diff --git a/sources/application/UI/Layout/UIContentBlock.php b/sources/application/UI/Layout/UIContentBlock.php new file mode 100644 index 000000000..9b4d9514b --- /dev/null +++ b/sources/application/UI/Layout/UIContentBlock.php @@ -0,0 +1,113 @@ +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); + } +} diff --git a/sources/application/UI/Layout/iUIContentBlock.php b/sources/application/UI/Layout/iUIContentBlock.php new file mode 100644 index 000000000..f20e94149 --- /dev/null +++ b/sources/application/UI/Layout/iUIContentBlock.php @@ -0,0 +1,56 @@ +GetId().': '.$message, $code, $previous); + } +} diff --git a/sources/application/UI/iUIBlock.php b/sources/application/UI/iUIBlock.php index dbe9a9db1..e20d63b20 100644 --- a/sources/application/UI/iUIBlock.php +++ b/sources/application/UI/iUIBlock.php @@ -115,5 +115,5 @@ interface iUIBlock * * @return $this */ - public function AddExtraHtmlContent(string $sHTML) :iUIBlock; + public function AddHtml(string $sHTML): iUIBlock; } diff --git a/sources/application/WebPage/AjaxPage.php b/sources/application/WebPage/AjaxPage.php index 8f7bd0217..21023afb6 100644 --- a/sources/application/WebPage/AjaxPage.php +++ b/sources/application/WebPage/AjaxPage.php @@ -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; } /** diff --git a/sources/application/WebPage/CLIPage.php b/sources/application/WebPage/CLIPage.php index dd1bcf07a..cad7e942f 100644 --- a/sources/application/WebPage/CLIPage.php +++ b/sources/application/WebPage/CLIPage.php @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with iTop. If not, see +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) diff --git a/sources/application/WebPage/CSVPage.php b/sources/application/WebPage/CSVPage.php index c1aeb7170..666ff1f67 100644 --- a/sources/application/WebPage/CSVPage.php +++ b/sources/application/WebPage/CSVPage.php @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with iTop. If not, see +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) diff --git a/sources/application/WebPage/NiceWebPage.php b/sources/application/WebPage/NiceWebPage.php index 15c859908..0e3eb1f54 100644 --- a/sources/application/WebPage/NiceWebPage.php +++ b/sources/application/WebPage/NiceWebPage.php @@ -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); } } diff --git a/sources/application/WebPage/Page.php b/sources/application/WebPage/Page.php index 8d8d748c7..567e485b7 100644 --- a/sources/application/WebPage/Page.php +++ b/sources/application/WebPage/Page.php @@ -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 diff --git a/sources/application/WebPage/TabManager.php b/sources/application/WebPage/TabManager.php index 60d40cc3d..a5530ce9f 100644 --- a/sources/application/WebPage/TabManager.php +++ b/sources/application/WebPage/TabManager.php @@ -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; } } diff --git a/sources/application/WebPage/UIBlockManager.php b/sources/application/WebPage/UIBlockManager.php deleted file mode 100644 index 1dc9aa17e..000000000 --- a/sources/application/WebPage/UIBlockManager.php +++ /dev/null @@ -1,129 +0,0 @@ -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; - } -} diff --git a/sources/application/WebPage/WebPage.php b/sources/application/WebPage/WebPage.php index 3fb13fa65..4dfd05df1 100644 --- a/sources/application/WebPage/WebPage.php +++ b/sources/application/WebPage/WebPage.php @@ -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 "\n"; - echo "\n"; - echo "\n"; - echo "\n"; - echo ""; - echo "".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."\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 "\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 "\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 "\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 "\n"; - } - // JS inline scripts - if (count($this->a_scripts) > 0) - { - echo "\n"; - } + // if (trim($s_captured_output) != "") { + // echo "
".utils::FilterXSS($s_captured_output)."
\n"; + // } - echo "\n"; - echo "\n"; - echo self::FilterXSS($this->s_content); - if (trim($s_captured_output) != "") - { - echo "
".self::FilterXSS($s_captured_output)."
\n"; - } - echo '
'.self::FilterXSS($this->s_deferred_content).'
'; - echo "\n"; - echo "\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 = '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('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 /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 /templates/) to the page template + * + * @return string + * @since 2.8.0 + */ + public function GetTemplateRelPath() + { + return $this->sTemplateRelPath; } } diff --git a/sources/application/WebPage/XMLPage.php b/sources/application/WebPage/XMLPage.php index 7f839b2b2..daa646f52 100644 --- a/sources/application/WebPage/XMLPage.php +++ b/sources/application/WebPage/XMLPage.php @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with iTop. If not, see +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) diff --git a/sources/application/WebPage/iTopWebPage.php b/sources/application/WebPage/iTopWebPage.php index 95231aeae..7aa55f4fb 100644 --- a/sources/application/WebPage/iTopWebPage.php +++ b/sources/application/WebPage/iTopWebPage.php @@ -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 /templates/) to the default page template */ + /** @var string DEFAULT_PAGE_TEMPLATE_REL_PATH The relative path (from /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 /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 /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 .= ' '; - $sHtml .= self::FilterXSS($this->s_content); + $sHtml .= utils::FilterXSS($this->s_content); $sHtml .= ' '; - } - elseif ($this->GetOutputFormat() == 'html') - { + } elseif ($this->GetOutputFormat() == 'html') { // Add the captured output - if (trim($s_captured_output) != "") - { - $sHtml .= "
".self::FilterXSS($s_captured_output)."
\n"; + if (trim($s_captured_output) != "") { + $sHtml .= "
".utils::FilterXSS($s_captured_output)."
\n"; } - $sHtml .= "
".self::FilterXSS($this->s_deferred_content)."
"; + $sHtml .= "
".utils::FilterXSS($this->s_deferred_content)."
"; $sHtml .= "
Please wait...
\n"; // jqModal Window $sHtml .= "
"; $sHtml .= "
"; - } - 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; } /** diff --git a/templates/components/html/layout.html.twig b/templates/components/html/layout.html.twig index 0be0b6d67..3da5b4e9f 100644 --- a/templates/components/html/layout.html.twig +++ b/templates/components/html/layout.html.twig @@ -1 +1,3 @@ -
{{ oUIBlock.GetHtml()|raw }}
\ No newline at end of file +{% apply spaceless %} + {{ oUIBlock.GetHtml()|raw }} +{% endapply %} \ No newline at end of file diff --git a/templates/layouts/contentblock/layout.html.twig b/templates/layouts/contentblock/layout.html.twig new file mode 100644 index 000000000..639a1ca5c --- /dev/null +++ b/templates/layouts/contentblock/layout.html.twig @@ -0,0 +1,9 @@ +{% apply spaceless %} + {#
#} + {% block iboContentBlockContainer %} + {% for oSubBlock in oUIBlock.GetSubBlocks() %} + {{ render_block(oSubBlock, {aPage: aPage}) }} + {% endfor %} + {% endblock %} + {#
#} +{% endapply %} \ No newline at end of file diff --git a/templates/layouts/contentblock/layout.js.twig b/templates/layouts/contentblock/layout.js.twig new file mode 100644 index 000000000..e69de29bb diff --git a/templates/layouts/tabcontainer/ajaxtab/layout.html.twig b/templates/layouts/tabcontainer/ajaxtab/layout.html.twig new file mode 100644 index 000000000..e69de29bb diff --git a/templates/layouts/tabcontainer/ajaxtab/layout.js.twig b/templates/layouts/tabcontainer/ajaxtab/layout.js.twig new file mode 100644 index 000000000..e69de29bb diff --git a/templates/layouts/tabcontainer/layout.html.twig b/templates/layouts/tabcontainer/layout.html.twig new file mode 100644 index 000000000..9aafb9cc4 --- /dev/null +++ b/templates/layouts/tabcontainer/layout.html.twig @@ -0,0 +1,25 @@ +
+ {% block iboTabContainer %} + +
+ + {% for oTab in oUIBlock.GetSubBlocks() %} + {% if oTab.GetType() == 'html' %} +
+ {{ render_block(oTab, {aPage: aPage}) }} +
+ {% endif %} + {% endfor %} +
+ + {% endblock %} +
+ diff --git a/templates/layouts/tabcontainer/layout.js.twig b/templates/layouts/tabcontainer/layout.js.twig new file mode 100644 index 000000000..a0527a4fc --- /dev/null +++ b/templates/layouts/tabcontainer/layout.js.twig @@ -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 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(); +} diff --git a/templates/layouts/tabcontainer/tab/layout.html.twig b/templates/layouts/tabcontainer/tab/layout.html.twig new file mode 100644 index 000000000..ae2d27929 --- /dev/null +++ b/templates/layouts/tabcontainer/tab/layout.html.twig @@ -0,0 +1,7 @@ +
+ {% block iboContentBlockContainer %} + {% for oSubBlock in oUIBlock.GetSubBlocks() %} + {{ render_block(oSubBlock, {aPage: aPage}) }} + {% endfor %} + {% endblock %} +
diff --git a/templates/layouts/tabcontainer/tab/layout.js.twig b/templates/layouts/tabcontainer/tab/layout.js.twig new file mode 100644 index 000000000..e69de29bb diff --git a/templates/pages/backoffice/ajaxpage/layout.html.twig b/templates/pages/backoffice/ajaxpage/layout.html.twig new file mode 100644 index 000000000..19f953369 --- /dev/null +++ b/templates/pages/backoffice/ajaxpage/layout.html.twig @@ -0,0 +1 @@ +{{ render_block(oLayout, {aPage: aPage}) }} diff --git a/templates/pages/backoffice/layout.html.twig b/templates/pages/backoffice/layout.html.twig index 499b1b92e..12456a3d9 100644 --- a/templates/pages/backoffice/layout.html.twig +++ b/templates/pages/backoffice/layout.html.twig @@ -1,38 +1,6 @@ - - - - - {# 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 %} - - {% endif %} - {{ aPage.sTitle }} - - +{% 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'] != '' %}{% 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 %} - - {% endfor %} - {% endblock %} - - +{% block iboPageBodyHtml %} {{ render_block(aLayouts.oNavigationMenu, {aPage: aPage}) }}
@@ -49,41 +17,10 @@
+{% endblock %} - {% block iboPageJsFiles %} - {% for sJsFile in aPage.aJsFiles %} - - {% endfor %} - {% endblock %} - - {% block iboPageJsInlineScripts %} - - - {% 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) #} - - {% endfor %} - {% endblock %} - {% endblock %} - - \ No newline at end of file +{% block iboPageJsInlineOnInit %} + {% for sJsInline in aPage.aJsInlineOnInit %} + {{ sJsInline|raw }} + {% endfor %} +{% endblock %} diff --git a/templates/pages/backoffice/nicewebpage/layout.html.twig b/templates/pages/backoffice/nicewebpage/layout.html.twig new file mode 100644 index 000000000..993e7a792 --- /dev/null +++ b/templates/pages/backoffice/nicewebpage/layout.html.twig @@ -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 %} + diff --git a/templates/pages/backoffice/webpage/layout.html.twig b/templates/pages/backoffice/webpage/layout.html.twig new file mode 100644 index 000000000..85be2a25d --- /dev/null +++ b/templates/pages/backoffice/webpage/layout.html.twig @@ -0,0 +1,77 @@ + + + + + {# 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 %} + + {% endif %} + {{ aPage.sTitle }} + {% if aPage.sFaviconUrl is defined %} + + {% endif %} + + + {# 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'] != '' %}{% 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 %} + + {% endfor %} + {% endblock %} + + +{% block iboPageBodyHtml %} +
+ {{ render_block(oLayout, {aPage: aPage}) }} + + {# TODO: Remove this when modal development is done #} +
{{ aPage.sDeferredContent|raw }}
+ + + +
+{% endblock %} +{% block iboPageJsFiles %} + {% for sJsFile in aPage.aJsFiles %} + + {% endfor %} +{% endblock %} + +{% block iboPageJsInlineScripts %} + + +{% 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) #} + + {% endfor %} +{% endblock %} +{% endblock %} + +