From af87a04d133ae887f57f4414621a871fab3e4ad1 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 6 Aug 2020 17:13:50 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B02847=20-=20Add=20page=20content=20layout?= =?UTF-8?q?=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/itopwebpage.class.inc.php | 74 +++++- css/backoffice/layout/_all.scss | 3 +- css/backoffice/layout/_content.scss | 29 ++ lib/composer/autoload_classmap.php | 3 + lib/composer/autoload_static.php | 3 + pages/UI.php | 15 +- .../UI/Layout/PageContent/PageContent.php | 247 ++++++++++++++++++ .../Layout/PageContent/PageContentFactory.php | 61 +++++ .../PageContentWithSideContent.php | 105 ++++++++ .../layouts/page-content/layout.html.twig | 15 ++ .../page-content/with-side-content.html.twig | 15 ++ 11 files changed, 564 insertions(+), 6 deletions(-) create mode 100644 css/backoffice/layout/_content.scss create mode 100644 sources/application/UI/Layout/PageContent/PageContent.php create mode 100644 sources/application/UI/Layout/PageContent/PageContentFactory.php create mode 100644 sources/application/UI/Layout/PageContent/PageContentWithSideContent.php create mode 100644 templates/layouts/page-content/layout.html.twig create mode 100644 templates/layouts/page-content/with-side-content.html.twig diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index d248e36e2..4f7c66156 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -24,6 +24,8 @@ require_once(APPROOT."/application/user.preferences.class.inc.php"); use Combodo\iTop\Application\TwigBase\Twig\TwigHelper; use Combodo\iTop\Application\UI\iUIBlock; use Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenuFactory; +use Combodo\iTop\Application\UI\Layout\PageContent\PageContent; +use Combodo\iTop\Application\UI\Layout\PageContent\PageContentFactory; use Combodo\iTop\Application\UI\Layout\TopBar\TopBarFactory; use Combodo\iTop\Application\UI\UIBlock; use Combodo\iTop\Renderer\BlockRenderer; @@ -40,10 +42,14 @@ 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; - private $m_sMenu; - // private $m_currentOrganization; + /** @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; @@ -69,6 +75,10 @@ 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()); + ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker'); if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST))) @@ -147,6 +157,31 @@ 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; + } + /** * */ @@ -811,6 +846,35 @@ JS return TopBarFactory::MakeStandard($this->GetBreadCrumbsNewEntry()); } + /** + * Set the content layout (main content, [side content,] manually added content, ...) + * This function is public as the developer needs to be able to set how the content will be displayed. + * + * @internal + * + * @param \Combodo\iTop\Application\UI\Layout\PageContent\PageContent $oLayout + * + * @return $this + * @since 2.8.0 + */ + public function SetContentLayout(PageContent $oLayout) + { + $this->oContentLayout = $oLayout; + return $this; + } + + /** + * Return the content layout (main content, [side content,] manually added content, ...) + * + * @internal + * @return \Combodo\iTop\Application\UI\Layout\PageContent\PageContent + * @since 2.8.0 + */ + protected function GetContentLayout() + { + return $this->oContentLayout; + } + /** * Return the new breadcrumbs entry or null if we don't create a new entry for the current page * @@ -1002,6 +1066,7 @@ EOF // TODO: Check if we can keep this as is // 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)); // Base structure of data to pass to the TWIG template $aData['aPage'] = [ @@ -1031,6 +1096,8 @@ EOF $aData['aLayouts']['oNavigationMenu'] = $this->GetNavigationMenuLayout(); // - Prepare top bar $aData['aLayouts']['oTopBar'] = $this->GetTopBarLayout(); + // - Prepare content + $aData['aLayouts']['oPageContent'] = $this->GetContentLayout(); // - Retrieve layouts linked files // Note: Adding them now instead of in the template allow us to remove duplicates and lower the browser parsing time /** @var \Combodo\iTop\Application\UI\UIBlock|string $oLayout */ @@ -1073,7 +1140,6 @@ EOF ); $oTwigEnv = TwigHelper::GetTwigEnvironment(APPROOT.'templates/'); - $sTemplateRelPath = 'pages/backoffice/layout'; // Send headers if ($this->GetOutputFormat() === 'html') @@ -1086,7 +1152,7 @@ EOF // Render final TWIG into global HTML $oKpi = new ExecutionKPI(); - $sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $sTemplateRelPath); + $sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $this->GetTemplateRelPath()); $oKpi->ComputeAndReport('TWIG rendering'); // Echo global HTML diff --git a/css/backoffice/layout/_all.scss b/css/backoffice/layout/_all.scss index 6bcf94b62..d025e61bf 100644 --- a/css/backoffice/layout/_all.scss +++ b/css/backoffice/layout/_all.scss @@ -17,4 +17,5 @@ */ @import "navigation-menu"; -@import "top-bar"; \ No newline at end of file +@import "top-bar"; +@import "content"; \ No newline at end of file diff --git a/css/backoffice/layout/_content.scss b/css/backoffice/layout/_content.scss new file mode 100644 index 000000000..60acf5974 --- /dev/null +++ b/css/backoffice/layout/_content.scss @@ -0,0 +1,29 @@ +/*! + * 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 + */ + +.ibo-center-container{ + +} +.ibo-center-container--with-side-content{ + display: flex; + align-items: stretch; + + #ibo-main-content{ + flex-grow: 1; /* To occupy maximum width, side content will handle its width */ + } +} \ No newline at end of file diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index ddb24e82f..7c608dfbd 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -166,6 +166,9 @@ return array( 'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreateHelper' => $baseDir . '/sources/application/UI/Component/QuickCreate/QuickCreateHelper.php', 'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenu' => $baseDir . '/sources/application/UI/Layout/NavigationMenu/NavigationMenu.php', 'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenuFactory' => $baseDir . '/sources/application/UI/Layout/NavigationMenu/NavigationMenuFactory.php', + 'Combodo\\iTop\\Application\\UI\\Layout\\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\\TopBar\\TopBar' => $baseDir . '/sources/application/UI/Layout/TopBar/TopBar.php', 'Combodo\\iTop\\Application\\UI\\Layout\\TopBar\\TopBarFactory' => $baseDir . '/sources/application/UI/Layout/TopBar/TopBarFactory.php', 'Combodo\\iTop\\Application\\UI\\UIBlock' => $baseDir . '/sources/application/UI/UIBlock.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 851978996..69256d760 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -396,6 +396,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Application\\UI\\Component\\QuickCreate\\QuickCreateHelper' => __DIR__ . '/../..' . '/sources/application/UI/Component/QuickCreate/QuickCreateHelper.php', 'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenu' => __DIR__ . '/../..' . '/sources/application/UI/Layout/NavigationMenu/NavigationMenu.php', 'Combodo\\iTop\\Application\\UI\\Layout\\NavigationMenu\\NavigationMenuFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/NavigationMenu/NavigationMenuFactory.php', + 'Combodo\\iTop\\Application\\UI\\Layout\\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\\TopBar\\TopBar' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TopBar/TopBar.php', 'Combodo\\iTop\\Application\\UI\\Layout\\TopBar\\TopBarFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/TopBar/TopBarFactory.php', 'Combodo\\iTop\\Application\\UI\\UIBlock' => __DIR__ . '/../..' . '/sources/application/UI/UIBlock.php', diff --git a/pages/UI.php b/pages/UI.php index 7637c23cb..e88741ce1 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -19,6 +19,7 @@ use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearchHelper; use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreateHelper; +use Combodo\iTop\Application\UI\Layout\PageContent\PageContentFactory; /** * Displays a popup welcome message, once per session at maximum @@ -448,7 +449,19 @@ try if (!is_null($oObj)) { SetObjectBreadCrumbEntry($oObj, $oP); - DisplayDetails($oP, $sClass, $oObj, $id); +// DisplayDetails($oP, $sClass, $oObj, $id); + + // The object could be listed, check if it is actually allowed to view it + $oSet = CMDBObjectSet::FromObject($oObj); + if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_NO) + { + throw new SecurityException('User not allowed to view this object', array('class' => $sClass, 'id' => $id)); + } + + $sClassLabel = MetaModel::GetName($sClass); + $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $oP->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObj)); + $oObj->DisplayDetails($oP); } } break; diff --git a/sources/application/UI/Layout/PageContent/PageContent.php b/sources/application/UI/Layout/PageContent/PageContent.php new file mode 100644 index 000000000..aed1b996c --- /dev/null +++ b/sources/application/UI/Layout/PageContent/PageContent.php @@ -0,0 +1,247 @@ + + * @package Combodo\iTop\Application\UI\Layout\PageContent + * @internal + * @since 2.8.0 + */ +class PageContent extends UIBlock +{ + // Overloaded constants + const BLOCK_CODE = 'ibo-page-content'; + const HTML_TEMPLATE_REL_PATH = 'layouts/page-content/layout'; + + /** @var string ENUM_CONTENT_AREA_MAIN The main content area */ + const ENUM_CONTENT_AREA_MAIN = 'main'; + + /** @var \Combodo\iTop\Application\UI\iUIBlock[][] $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; + + /** + * PageContent constructor. + * + * @param string $sId + */ + public function __construct($sId = null) + { + parent::__construct($sId); + + $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($sAreaId, $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($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($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($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($sAreaId, $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. + * + * @param \Combodo\iTop\Application\UI\iUIBlock[] $aBlocks + * + * @return $this + */ + public function SetMainBlocks($aBlocks) + { + $this->SetContentAreaBlocks(static::ENUM_CONTENT_AREA_MAIN, $aBlocks); + return $this; + } + + /** + * Return all the main blocks + * + * @return \Combodo\iTop\Application\UI\iUIBlock[] + * @throws \Exception + */ + public function GetMainBlocks() + { + return $this->GetContentAreaBlocks(static::ENUM_CONTENT_AREA_MAIN); + } + + /** + * Add the $oBlock to the main blocks. + * Note that if a block with the same ID already exists, it will be replaced. + * + * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock + * + * @return $this + */ + public function AddMainBlock(iUIBlock $oBlock) + { + $this->AddBlockToContentArea(static::ENUM_CONTENT_AREA_MAIN, $oBlock); + return $this; + } + + /** + * Remove the main block identified by $sBlockId. + * Note that if no block with that ID exists, it will proceed silently. + * + * @param string $sBlockId + * + * @return $this + */ + public function RemoveMainBlock($sBlockId) + { + $this->RemoveBlockFromContentArea(static::ENUM_CONTENT_AREA_MAIN, $sBlockId); + return $this; + } + + /** + * Set the extra HTML content + * + * @param string $sExtraHtmlContent + * + * @return $this + */ + public function SetExtraHtmlContent($sExtraHtmlContent) + { + $this->sExtraHtmlContent = $sExtraHtmlContent; + return $this; + } + + /** + * Get the extra HTML content as-is, no processing is done on it + * + * @return string + */ + public function GetExtraHtmlContent() + { + return $this->sExtraHtmlContent; + } + + /** + * @inheritDoc + * @throws \Exception + */ + public function GetSubBlocks() + { + $aSubBlocks = []; + foreach($this->EnumContentAreas() as $sAreaId) + { + foreach($this->GetContentAreaBlocks($sAreaId) as $oBlock) + { + $aSubBlocks[$oBlock->GetId()] = $oBlock; + } + } + + return $aSubBlocks; + } +} \ No newline at end of file diff --git a/sources/application/UI/Layout/PageContent/PageContentFactory.php b/sources/application/UI/Layout/PageContent/PageContentFactory.php new file mode 100644 index 000000000..c7c12b252 --- /dev/null +++ b/sources/application/UI/Layout/PageContent/PageContentFactory.php @@ -0,0 +1,61 @@ + + * @package Combodo\iTop\Application\UI\Layout\PageContent + * @since 2.8.0 + */ +class PageContentFactory +{ + /** + * Make a standard empty PageContent layout for backoffice pages. + * + * @return \Combodo\iTop\Application\UI\Layout\PageContent\PageContent + */ + public static function MakeStandardEmpty() + { + return new PageContent(); + } + + /** + * Make a standard object details page with the form in the middle and the logs / activity in the side panel + * + * @param \DBObject $oObject + * + * @return \Combodo\iTop\Application\UI\Layout\PageContent\PageContentWithSideContent + */ + public static function MakeForObjectDetails(DBObject $oObject) + { + $oLayout = new PageContentWithSideContent(); + + // Add object details layout + // Add object activity layout + + return $oLayout; + } +} \ No newline at end of file diff --git a/sources/application/UI/Layout/PageContent/PageContentWithSideContent.php b/sources/application/UI/Layout/PageContent/PageContentWithSideContent.php new file mode 100644 index 000000000..859c6873d --- /dev/null +++ b/sources/application/UI/Layout/PageContent/PageContentWithSideContent.php @@ -0,0 +1,105 @@ + + * @package Combodo\iTop\Application\UI\Layout\PageContent + * @internal + * @since 2.8.0 + */ +class PageContentWithSideContent extends PageContent +{ + // Overloaded constants + const BLOCK_CODE = 'ibo-page-content-with-side-content'; + const HTML_TEMPLATE_REL_PATH = 'layouts/page-content/with-side-content'; + + // Specific constants + const ENUM_CONTENT_AREA_SIDE = 'side'; + + /** + * PageContentWithSideContent constructor. + * + * @param string $sId + */ + public function __construct($sId = null) + { + parent::__construct($sId); + + $this->SetSideBlocks([]); + } + + /** + * Set all side blocks at once. + * + * @param \Combodo\iTop\Application\UI\iUIBlock[] $aBlocks + * + * @return $this + */ + public function SetSideBlocks($aBlocks) + { + $this->SetContentAreaBlocks(static::ENUM_CONTENT_AREA_SIDE, $aBlocks); + return $this; + } + + /** + * Return all the side blocks + * + * @return \Combodo\iTop\Application\UI\iUIBlock[] + * @throws \Exception + */ + public function GetSideBlocks() + { + return $this->GetContentAreaBlocks(static::ENUM_CONTENT_AREA_SIDE); + } + + /** + * Add the $oBlock to the side blocks. + * Note that if a block with the same ID already exists, it will be replaced. + * + * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock + * + * @return $this + */ + public function AddSideBlock(iUIBlock $oBlock) + { + $this->AddBlockToContentArea(static::ENUM_CONTENT_AREA_SIDE, $oBlock); + return $this; + } + + /** + * Remove the side block identified by $sBlockId. + * Note that if no block with that ID exists, it will proceed silently. + * + * @param string $sBlockId + * + * @return $this + */ + public function RemoveSideBlock($sBlockId) + { + $this->RemoveBlockFromContentArea(static::ENUM_CONTENT_AREA_SIDE, $sBlockId); + return $this; + } +} \ No newline at end of file diff --git a/templates/layouts/page-content/layout.html.twig b/templates/layouts/page-content/layout.html.twig new file mode 100644 index 000000000..ec2da4818 --- /dev/null +++ b/templates/layouts/page-content/layout.html.twig @@ -0,0 +1,15 @@ +
+ {% block iboPageCenterContainer %} +
+ {% block iboPageMainContent %} + Before GetMainBlocks + {% for oSubBlock in oUIBlock.GetMainBlocks() %} + {{ render_block(oSubBlock, {aPage: aPage}) }} + {% endfor %} + {% endblock %} + {% block iboPageExtraHtml %} + {{ oUIBlock.GetExtraHtmlContent()|raw }} + {% endblock %} +
+ {% endblock %} +
\ No newline at end of file diff --git a/templates/layouts/page-content/with-side-content.html.twig b/templates/layouts/page-content/with-side-content.html.twig new file mode 100644 index 000000000..44ed83b7e --- /dev/null +++ b/templates/layouts/page-content/with-side-content.html.twig @@ -0,0 +1,15 @@ +{% extends 'layouts/page-content/layout.html.twig' %} + +{% block iboPageCenterContainerExtraClasses %}{{ parent() }} ibo-center-container--with-side-content{% endblock %} + +{% block iboPageCenterContainer %} + {{ parent() }} + +{% endblock %} \ No newline at end of file