Skip to content
Snippets Groups Projects
Commit 125f32b8 authored by Paul Ilea's avatar Paul Ilea
Browse files

[TASK] BE Module update

- Alternative page links (if no category page selected)
- BE listing with filters
- Multilanguage feature
- Edit page content link
- User restrictions respected
- Licensing service
- News/Category records creation links
parent 8d47e382
No related branches found
No related tags found
1 merge request!2Feature be module
Showing
with 1376 additions and 291 deletions
...@@ -26,12 +26,20 @@ namespace SGalinski\SgNews\Controller; ...@@ -26,12 +26,20 @@ namespace SGalinski\SgNews\Controller;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use SGalinski\SgNews\Service\Backend\Utility; use In2code\Powermail\Utility\LocalizationUtility;
use SGalinski\SgNews\Service\BackendNewsUtility;
use SGalinski\SgNews\Service\LicensingService;
use TYPO3\CMS\Backend\Clipboard\Clipboard;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent; use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
/** /**
* News Controller * News Controller
...@@ -39,55 +47,252 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; ...@@ -39,55 +47,252 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class BackendController extends ActionController { class BackendController extends ActionController {
/** /**
* DocHeaderComponent * The uid of the current page
* *
* @var DocHeaderComponent * @var int
*/ */
protected $docHeaderComponent; private $pageUid = 0;
/** /**
* @var \SGalinski\SgNews\Domain\Repository\NewsRepository * The info fields of the current page
* @inject *
* @var array
*/ */
private $newsRepository; private $pageInfo = [];
/** /**
* @var \SGalinski\SgNews\Domain\Repository\CategoryRepository * The uid of the current root page
* @inject *
* @var int
*/ */
private $categoryRepository; private $rootPageUid = 0;
/** /**
* @var \SGalinski\SgNews\Domain\Repository\TagRepository * Command array on the form [tablename][uid][command] = value.
* @inject * This array may get additional data set internally based on clipboard commands send in CB var!
*
* @var array
*/ */
private $tagRepository; private $command;
/** /**
* @param array $filters * Clipboard command array. May trigger changes in "cmd"
*
* @var array
*/
private $clipboardCommandArray;
/**
* Currently selected language
*
* @var int
*/
public $language;
/**
* DocHeaderComponent
*
* @var DocHeaderComponent
*/
private $docHeaderComponent;
/**
* Initialize action for all actions
*
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/
public function initializeAction() {
$this->command = GeneralUtility::_GP('cmd');
$this->clipboardCommandArray = GeneralUtility::_GP('CB');
$this->initClipboard();
}
/**
* Initializes the view before invoking an action method.
*
* Override this method to solve assign variables common for all actions
* or prepare the view in another way before the action is called.
*
* @param ViewInterface $view The view to be initialized
*
* @return void
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException * @throws \InvalidArgumentException
* @api
*/ */
public function indexAction(array $filters = []) { protected function initializeView(ViewInterface $view) {
// create doc header component // create doc header component
$pageUid = (int) GeneralUtility::_GP('id'); $this->pageUid = (int) GeneralUtility::_GP('id');
/** @var BackendUserAuthentication $backendUser */ /** @var BackendUserAuthentication $backendUser */
$backendUser = $GLOBALS['BE_USER']; $backendUser = $GLOBALS['BE_USER'];
$pageInfo = BackendUtility::readPageAccess($pageUid, $backendUser->getPagePermsClause(1)); $this->pageInfo = BackendUtility::readPageAccess($this->pageUid, $backendUser->getPagePermsClause(1));
$this->docHeaderComponent = GeneralUtility::makeInstance(DocHeaderComponent::class); if ($this->pageInfo) {
$this->docHeaderComponent->setMetaInformation($pageInfo); $this->docHeaderComponent = GeneralUtility::makeInstance(DocHeaderComponent::class);
Utility::makeButtons($this->docHeaderComponent, $this->request); if ($this->pageUid) {
$this->rootPageUid = BackendNewsUtility::getRootUidByPageUid($this->pageUid);
// retrieve next site root id }
$siteRootId = Utility::getSiteRoot((int) GeneralUtility::_GP('id'));
$categories = Utility::getCategoriesForSiteRoot($siteRootId); /** @var BackendUserAuthentication $backendUser */
$news = Utility::getAllNewsByCategories($categories, $filters); $backendUser = $GLOBALS['BE_USER'];
$this->view->assign('docHeader', $this->docHeaderComponent->docHeaderContent()); $menuSettings = GeneralUtility::_GP('SET') ?: [];
$this->view->assign('pageUid', $pageUid); foreach ($menuSettings as $key => $menuSetting) {
$this->view->assign('categories', $categories); $backendUser->pushModuleData('tools_beuser/index.php/web_SgNewsNews_' . $key, $menuSetting);
$this->view->assign('news', $news); }
$this->view->assign('filters', $filters); $this->language = $backendUser->getModuleData(
'tools_beuser/index.php/web_SgNewsNews_language', 'ses'
) ?: 0;
$this->docHeaderComponent->setMetaInformation($this->pageInfo);
$this->makeButtons();
$this->makeLanguageMenu();
$this->view->assign('pageUid', $this->pageUid);
$this->view->assign('rootPageUid', $this->rootPageUid);
$this->view->assign('docHeader', $this->docHeaderComponent->docHeaderContent());
$this->view->assign('typo3Version', VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version));
if (!LicensingService::checkKey()) {
$this->view->assign('showLicenseBanner', TRUE);
}
}
}
/**
* @param array $filters
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException
*/
public function indexAction(array $filters = NULL) {
$showNewsList = FALSE;
if (
($this->pageUid && $this->pageUid === $this->rootPageUid) ||
(int) $this->pageInfo['doktype'] === BackendNewsUtility::CATEGORY_DOKTYPE
) {
/** @var BackendUserAuthentication $backendUser */
$backendUser = $GLOBALS['BE_USER'];
if ($filters === NULL) {
$filters = $backendUser->getModuleData('tools_beuser/index.php/web_SgNewsNews_filters', 'ses') ?: [];
} else {
$backendUser->pushModuleData('tools_beuser/index.php/web_SgNewsNews_filters', $filters);
}
if ((int) $this->pageInfo['doktype'] !== BackendNewsUtility::CATEGORY_DOKTYPE) {
$categories = BackendNewsUtility::getCategoriesForSiteRoot($this->rootPageUid);
$this->view->assign('categories', $categories);
$this->view->assign('showCategoryFilter', TRUE);
} else {
$filters['categories'] = [$this->pageUid];
}
$tags = BackendNewsUtility::getTagsForPage($this->pageUid, $this->language);
$news = BackendNewsUtility::getNewsByFilters($this->rootPageUid, $filters, $this->language);
$this->view->assign('tags', $tags);
$this->view->assign('news', $news);
$this->view->assign('filters', $filters);
$showNewsList = TRUE;
} else {
$alternativePageOptions = BackendNewsUtility::getAlternativePageOptions();
$this->view->assign('alternativePageOptions', $alternativePageOptions);
}
$this->view->assign('showNewsList', $showNewsList);
}
/**
* create buttons for the backend module header
*
* @return void
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
*/
private function makeButtons() {
/** @var ButtonBar $buttonBar */
$buttonBar = $this->docHeaderComponent->getButtonBar();
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
// Refresh
$refreshButton = $buttonBar->makeLinkButton()
->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
->setTitle(LocalizationUtility::translate('LLL:EXT:lang/locallang_core.xlf:labels.reload', ''))
->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
$buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT);
// shortcut button
$shortcutButton = $buttonBar->makeShortcutButton()
->setModuleName($this->request->getPluginName())
->setGetVariables(
[
'id',
'M'
]
)
->setSetVariables([]);
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
$this->docHeaderComponent->getButtonBar();
}
/**
* create buttons for the backend module header
*
* @return void
* @throws \InvalidArgumentException
*/
private function makeLanguageMenu() {
if (!LicensingService::checkKey()) {
return;
}
$languageMenu = $this->docHeaderComponent->getMenuRegistry()->makeMenu();
$languageMenu->setIdentifier('languageMenu');
$languages = BackendNewsUtility::getAvailableLanguages($this->pageUid);
foreach ($languages as $key => $language) {
$menuItem = $languageMenu
->makeMenuItem()
->setTitle($language['title'])
->setHref(
BackendUtility::getModuleUrl('web_SgNewsNews') . '&id=' . $this->pageUid . '&SET[language]=' . $key
);
if ((int) $this->language === $key) {
$menuItem->setActive(TRUE);
}
$languageMenu->addMenuItem($menuItem);
}
$this->docHeaderComponent->getMenuRegistry()->addMenu($languageMenu);
}
/**
* Clipboard pasting and deleting.
*
* @throws \InvalidArgumentException
*/
public function initClipboard() {
if (is_array($this->clipboardCommandArray)) {
$clipObj = GeneralUtility::makeInstance(Clipboard::class);
$clipObj->initializeClipboard();
if ($this->clipboardCommandArray['paste']) {
$clipObj->setCurrentPad($this->clipboardCommandArray['pad']);
$this->command = $clipObj->makePasteCmdArray(
$this->clipboardCommandArray['paste'],
$this->command,
$this->clipboardCommandArray['update'] ?? NULL
);
}
if ($this->clipboardCommandArray['delete']) {
$clipObj->setCurrentPad($this->clipboardCommandArray['pad']);
$this->command = $clipObj->makeDeleteCmdArray($this->command);
}
if ($this->clipboardCommandArray['el']) {
$this->clipboardCommandArray['setP'] = 'normal';
$clipObj->setCmd($this->clipboardCommandArray);
$clipObj->cleanCurrent();
$clipObj->endClipboard();
}
}
} }
} }
<?php
namespace SGalinski\SgNews\Hooks;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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 General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SGalinski\SgNews\Service\BackendNewsUtility;
use TYPO3\CMS\Backend\Utility\BackendUtility;
/**
* Backend edid form hook
*/
class EditDocumentController {
/**
* Sets the default value for the pages author field
* to the name of the logged-in user
*
* @param \TYPO3\CMS\Backend\Controller\EditDocumentController $controller Default configuration
* @return void
*/
public function preInitAfter($controller) {
if (isset($controller->editconf['pages']) && $GLOBALS['BE_USER']->user['realName'] !== '') {
$doktype = 0;
foreach ($controller->editconf['pages'] as $pageUid => $command) {
if ($command === 'edit') {
$pageRow = BackendUtility::getRecord('pages', (int) $pageUid);
if ($pageRow && isset($pageRow['doktype'])) {
$doktype = (int) $pageRow['doktype'];
}
} elseif ($command === 'new' && isset($controller->overrideVals['pages']['doktype'])) {
$doktype = (int) $controller->overrideVals['pages']['doktype'];
}
break;
}
if ($doktype === BackendNewsUtility::NEWS_DOKTYPE) {
$GLOBALS['BE_USER']->userTS['TCAdefaults.']['pages.']['author'] = $GLOBALS['BE_USER']->user['realName'];
}
}
}
}
<?php
namespace SGalinski\SgNews\Service\Backend;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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 General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SGalinski\SgNews\Domain\Repository\NewsRepository;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\DatabaseConnection;
use TYPO3\CMS\Core\Database\QueryGenerator;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Request;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Class Utility
*/
class Utility {
const CATEGORY_DOKTYPE = 117;
const NEWS_DOKTYPE = 117;
/**
* create buttons for the backend module header
*
* @param $docHeaderComponent DocHeaderComponent
* @param $request Request
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
*/
public static function makeButtons(&$docHeaderComponent, &$request) {
/** @var ButtonBar $buttonBar */
$buttonBar = $docHeaderComponent->getButtonBar();
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
// Refresh
$refreshButton = $buttonBar->makeLinkButton()
->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
->setTitle(LocalizationUtility::translate('LLL:EXT:lang/locallang_core.xlf:labels.reload', ''))
->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
$buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT);
// shortcut button
$shortcutButton = $buttonBar->makeShortcutButton()
->setModuleName($request->getPluginName())
->setGetVariables(
[
'id',
'M'
]
)
->setSetVariables([]);
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
$docHeaderComponent->getButtonBar();
}
/**
* Retrieves the next site root in the page hierarchy from the current page
*
* @param int $currentPid
* @return int
*/
public static function getSiteRoot($currentPid) {
$rootLine = BackendUtility::BEgetRootLine((int) $currentPid);
$siteRoot = ['uid' => 0];
foreach ($rootLine as $page) {
if ($page['is_siteroot'] === '1') {
$siteRoot = $page;
break;
}
}
return $siteRoot['uid'];
}
/**
* Get an array of all category uids => >titles below the given site root id
*
* @param $siteRootPid
* @throws \InvalidArgumentException
* @return array
*/
public static function getCategoriesForSiteRoot($siteRootPid) {
// get all pageids below the given siteroot
$queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
$childPids = $queryGenerator->getTreeList($siteRootPid, PHP_INT_MAX, 0, 1);
// if doktype = 117 (category) then get the category name (page title)
/** @var DatabaseConnection $databaseConnection */
$databaseConnection = $GLOBALS['TYPO3_DB'];
$where = 'doktype = ' . self::CATEGORY_DOKTYPE . ' AND uid in (' . $childPids . ')';
$result = $databaseConnection->exec_SELECTquery('uid, title', 'pages', $where)->fetch_all();
$categories = [];
/** @var array $result */
foreach ($result as $item) {
$categories[$item[0]] = $item[1];
}
return $categories;
}
/**
* Get all news for the given categories (all if filter is empty)
*
* @param array $categories
* @param array $filters
* @return array
* @throws \InvalidArgumentException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
*/
public static function getAllNewsByCategories(array $categories = [], array $filters = []) {
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** @var NewsRepository $newsRepository */
$newsRepository = $objectManager->get(NewsRepository::class);
// filter by category if set
if (!empty($filters['filters']['categories'])) {
foreach ($categories as $key => $value) {
if (is_array($filters['filters']['categories'])) {
$unset = TRUE;
foreach($filters['filters']['categories'] as $category) {
if ($key == (int) $category) {
$unset = FALSE;
}
}
if ($unset) {
unset($categories[$key]);
}
}
}
}
$categoryIds = [];
foreach ($categories as $key => $value) {
$categoryIds[] = $key;
}
if (empty($categoryIds)) {
return [];
}
return $newsRepository->findAllSortedNewsByCategories($categoryIds, 0, 0, 'date', NULL, $raw = TRUE);
}
}
<?php
namespace SGalinski\SgNews\Service;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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 General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SGalinski\SgNews\Domain\Repository\TagRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\DatabaseConnection;
use TYPO3\CMS\Core\Database\QueryGenerator;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Class Utility
*/
class BackendNewsUtility {
/**
* @var int The category page doktype
*/
const CATEGORY_DOKTYPE = 117;
/**
* @var int The news page doktype
*/
const NEWS_DOKTYPE = 116;
/**
* Retrieves the next site root in the page hierarchy from the current page
*
* @param int $currentPid
* @return int
*/
public static function getRootUidByPageUid($currentPid) {
$rootLine = BackendUtility::BEgetRootLine((int) $currentPid);
$siteRoot = ['uid' => 0];
foreach ($rootLine as $page) {
if ((int) $page['is_siteroot'] === 1) {
$siteRoot = $page;
break;
}
}
return (int) $siteRoot['uid'];
}
/**
* Get an array of alternative pages for the BE Module view
*
* @return array
* @throws \InvalidArgumentException
*/
public static function getAlternativePageOptions() {
$options = [];
/** @var array $rootOptionRows */
$rootOptionRows = BackendUtility::getRecordsByField('pages', 'is_siteroot', 1, '', '', 'sorting');
if ($rootOptionRows) {
foreach ($rootOptionRows as $row) {
$pageInfo = BackendUtility::readPageAccess($row['uid'], $GLOBALS['BE_USER']->getPagePermsClause(1));
if ($pageInfo) {
$options[] = self::getOptionPageInfo($pageInfo);
}
$categories = self::getCategoriesForSiteRoot((int) $row['uid']);
/** @var int $categoryUid */
foreach ($categories as $categoryUid => $categoryTitle) {
if ((int) $pageInfo['uid'] !== $categoryUid) {
$categoryPageInfo = BackendUtility::readPageAccess(
$categoryUid, $GLOBALS['BE_USER']->getPagePermsClause(1)
);
if ($categoryPageInfo) {
$options[] = self::getOptionPageInfo($categoryPageInfo);
}
}
}
}
}
return $options;
}
/**
* Get an array of alternative pages for the BE Module view
*
* @param array $pageInfo
* @return array
*/
private static function getOptionPageInfo(array $pageInfo = []) {
if (isset($pageInfo['uid']) && (int) $pageInfo['uid']) {
$rootline = BackendUtility::BEgetRootLine($pageInfo['uid'], '', TRUE);
ksort($rootline);
$path = '/root';
foreach ($rootline as $page) {
$path .= '/p' . dechex($page['uid']);
}
$pageInfo['path'] = $path;
$pageInfo['_thePathFull'] = substr($pageInfo['_thePathFull'], 1);
$pageInfo['_thePathFull'] = substr($pageInfo['_thePathFull'], 0, -1);
}
return $pageInfo;
}
/**
* Get an array of all category uids => titles below the given site root id
*
* @param int $siteRootUid
* @return array
* @throws \InvalidArgumentException
*/
public static function getCategoriesForSiteRoot($siteRootUid) {
$siteRootUid = (int) $siteRootUid;
// get all pageids below the given siteroot
$queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
$childPids = $queryGenerator->getTreeList(
$siteRootUid, PHP_INT_MAX, 0, $GLOBALS['BE_USER']->getPagePermsClause(1)
);
// if doktype = 117 (category) then get the category name (page title)
/** @var DatabaseConnection $databaseConnection */
$databaseConnection = $GLOBALS['TYPO3_DB'];
$where = 'deleted = 0 AND doktype = ' . self::CATEGORY_DOKTYPE . ' AND uid in (' . $childPids . ')';
$result = $databaseConnection->exec_SELECTgetRows('uid, title', 'pages', $where);
$categories = [];
/** @var array $result */
foreach ($result as $page) {
$categoryPageInfo = BackendUtility::readPageAccess(
(int) $page['uid'], $GLOBALS['BE_USER']->getPagePermsClause(1)
);
if ($categoryPageInfo) {
$categories[(int) $page['uid']] = $page['title'];
}
}
return $categories;
}
/**
* Get an array of all tags uids => titles below the given site root id
*
* @param int $pageUid
* @param int $languageUid
* @return array
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
* @throws \InvalidArgumentException
*/
public static function getTagsForPage($pageUid, $languageUid = 0) {
$temporaryTSFEInstance = FALSE;
if (!isset($GLOBALS['TSFE'])) {
$temporaryTSFEInstance = TRUE;
$GLOBALS['TSFE'] = new \stdClass();
$GLOBALS['TSFE']->gr_list = '';
}
$pageUid = (int) $pageUid;
$languageUid = (int) $languageUid;
$tags = [];
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$tagRepository = $objectManager->get(TagRepository::class);
$query = $tagRepository->createQuery();
$querySettings = $query->getQuerySettings();
$querySettings->setLanguageUid($languageUid);
$querySettings->setLanguageOverlayMode(TRUE);
$querySettings->setLanguageMode('content_fallback');
$query->setQuerySettings($querySettings);
if ($pageUid) {
$rootline = BackendUtility::BEgetRootLine($pageUid, '', TRUE);
$pageTS = BackendUtility::getPagesTSconfig($pageUid, $rootline);
$tagsPid = 0;
if (isset($pageTS['TCEFORM.']['pages.']['tx_sgnews_tags.']['PAGE_TSCONFIG_ID'])) {
$tagsPid = (int) $pageTS['TCEFORM.']['pages.']['tx_sgnews_tags.']['PAGE_TSCONFIG_ID'];
}
if ($tagsPid) {
$query->matching($query->equals('pid', $tagsPid));
}
}
$query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
$resultTags = $query->execute(TRUE);
if ($temporaryTSFEInstance) {
unset($GLOBALS['TSFE']);
}
foreach ($resultTags as $tag) {
$tags[(int) $tag['uid']] = trim($tag['title']);
}
return $tags;
}
/**
* Get all news for the given categories (all if filter is empty)
*
* @param int $rootPageUid
* @param array $filters
* @param int $languageUid
* @return array
* @throws \InvalidArgumentException
*/
public static function getNewsByFilters($rootPageUid = 0, array $filters = [], $languageUid = 0) {
$out = [];
$rootPageUid = (int) $rootPageUid;
$languageUid = (int) $languageUid;
if (!$rootPageUid) {
return $out;
}
$categories = [];
if (!isset($filters['categories']) || !is_array($filters['categories']) || !count($filters['categories'])) {
$rootCategories = self::getCategoriesForSiteRoot($rootPageUid);
foreach ($rootCategories as $categoryUid => $categoryTitle) {
$categories[] = (int) $categoryUid;
}
} else {
foreach ($filters['categories'] as $categoryUid) {
$categoryPageInfo = BackendUtility::readPageAccess(
(int) $categoryUid, $GLOBALS['BE_USER']->getPagePermsClause(1)
);
if ($categoryPageInfo) {
$categories[] = (int) $categoryUid;
}
}
}
if (!count($categories)) {
return $out;
}
$queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
$allowedUids = [];
foreach ($categories as $categoryUid) {
$allowedUidsTemp = GeneralUtility::intExplode(
',', $queryGenerator->getTreeList(
$categoryUid, 1, 0, $GLOBALS['BE_USER']->getPagePermsClause(1)
), TRUE
);
$allowedUids = array_unique(array_merge($allowedUids, $allowedUidsTemp));
}
if (!count($allowedUids)) {
return $out;
}
list($select, $tables, $where) = self::getNewsQueryParts($allowedUids, $filters, $languageUid);
if ($tables === '') {
return $out;
}
/** @var DatabaseConnection $databaseConnection */
$databaseConnection = $GLOBALS['TYPO3_DB'];
$result = $databaseConnection->exec_SELECTquery($select, $tables, $where, '`pages`.`uid`', '`pages`.`sorting`');
while ($row = $result->fetch_assoc()) {
$out[] = $row;
}
return $out;
}
/**
* Creates news query parts
*
* @param array $allowedUids
* @param array $filters
* @param int $languageUid
* @return array
*/
private static function getNewsQueryParts(array $allowedUids, array $filters = [], $languageUid = 0) {
$out = [0 => '
`pages`.`uid` AS uid,
`pages`.`pid` AS pid,
`pages`.`hidden` AS hidden,
`pages`.`sorting` AS sorting,
`pages`.`doktype` AS doktype,
`pages`.`title` AS title
', 1 => '', 2 => ''];
$out[1] = '`pages`';
$out[2] = '`pages`.`uid` IN(' . implode(',', $allowedUids) . ') ' . BackendUtility::deleteClause('pages') .
' AND `pages`.`doktype` = ' . self::NEWS_DOKTYPE;
if ($languageUid) {
$out[0] .= ', `translation`.`title` AS translation_title';
$out[1] .= ' LEFT JOIN `pages_language_overlay` AS `translation` ON `translation`.`pid` = `pages`.`uid` ' .
BackendUtility::deleteClause('pages_language_overlay', 'translation') .
' AND `translation`.`sys_language_uid` = ' . $languageUid;
}
if (isset($filters['tags']) && is_array($filters['tags']) && count($filters['tags'])) {
$tagUids = [];
foreach ($filters['tags'] as $tagUid) {
if ((int) $tagUid && !in_array((int) $tagUid, $tagUids, TRUE)) {
$tagUids[] = (int) $tagUid;
}
}
if (count($tagUids)) {
$out[1] .= ' INNER JOIN `sys_category_record_mm` AS `tag` ON `tag`.`tablenames` = \'pages\'' .
' AND `tag`.`fieldname` = \'tx_sgnews_tags\' AND `tag`.`uid_foreign` = `pages`.`uid`' .
' AND `tag`.`uid_local` IN (' . implode(',', $filters['tags']) . ')';
}
}
if (isset($filters['search']) && trim($filters['search'])) {
$out[2] .= self::getNewsSearchClause(trim($filters['search']), $languageUid);
}
return $out;
}
/**
* Creates constraints of query for searching news by search-word
*
* @param string $searchString
* @param int $languageUid
* @return string
*/
private static function getNewsSearchClause($searchString = '', $languageUid = 0) {
$out = '';
$searchString = strtolower(trim($searchString));
$languageUid = (int) $languageUid;
if (!$searchString) {
return $out;
}
$out = ' AND (';
/** @var DatabaseConnection $databaseConnection */
$databaseConnection = $GLOBALS['TYPO3_DB'];
$likeString = 'LIKE \'%' . $databaseConnection->escapeStrForLike($searchString, 'pages') . '%\'';
$constraints = [];
$pageFields = [
'title' => TRUE,
'description' => TRUE,
'author' => TRUE,
'abstract' => TRUE,
];
foreach ($pageFields as $fieldName => $isCommonField) {
if ($isCommonField) {
$constraints[] = 'LOWER(`pages`.`' . $fieldName . '`) ' . $likeString;
}
}
if (!$languageUid) {
foreach ($pageFields as $fieldName => $isCommonField) {
if (!$isCommonField) {
$constraints[] = 'LOWER(`pages`.`' . $fieldName . '`) ' . $likeString;
}
}
} else {
foreach ($pageFields as $fieldName => $isCommonField) {
if ($isCommonField) {
$constraints[] = 'LOWER(`translation`.`' . $fieldName . '`) ' . $likeString;
}
}
}
$out .= implode(' OR ', $constraints) . ')';
return $out;
}
/**
* Returns the available languages for the current BE user
*
* @param int $pageUid
* @return array
* @throws \InvalidArgumentException
*/
public static function getAvailableLanguages($pageUid = 0) {
$pageUid = (int) $pageUid;
$rootline = BackendUtility::BEgetRootLine($pageUid, '', TRUE);
$defaultLanguage = LocalizationUtility::translate('backend.language.default', 'SgNews');
$pageTS = BackendUtility::getPagesTSconfig($pageUid, $rootline);
if (isset($pageTS['mod.']['SHARED.']['defaultLanguageLabel'])) {
$defaultLanguage = $pageTS['mod.']['SHARED.']['defaultLanguageLabel'] . ' (' . $defaultLanguage . ')';
}
$defaultLanguageFlag = 'empty-empty';
if (isset($pageTS['mod.']['SHARED.']['defaultLanguageFlag'])) {
$defaultLanguageFlag = 'flags-' . $pageTS['mod.']['SHARED.']['defaultLanguageFlag'];
}
$languages = [
0 => ['title' => $defaultLanguage, 'flag' => $defaultLanguageFlag]
];
/** @var DatabaseConnection $databaseConnection */
$databaseConnection = $GLOBALS['TYPO3_DB'];
$languageRows = $databaseConnection->exec_SELECTgetRows(
'uid, title, flag', 'sys_language', 'hidden = 0', '', 'sorting'
);
if ($languageRows) {
/** @var BackendUserAuthentication $backendUser */
$backendUser = $GLOBALS['BE_USER'];
foreach ($languageRows as $languageRow) {
if ($backendUser->checkLanguageAccess($languageRow['uid'])) {
$languages[(int) $languageRow['uid']] = [
'title' => $languageRow['title'],
'flag' => 'flags-' . $languageRow['flag'],
];
}
}
}
return $languages;
}
}
<?php
namespace SGalinski\SgNews\Service;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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 General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Class SGalinski\SgNews\Service\LicensingService
*/
class LicensingService {
/**
* Licensing Service Url
*/
const URL = 'https://www.sgalinski.de/?eID=sgLicensing';
/**
* Licensing Service Url
*/
const EXTENSION_KEY = 'sg_news';
/** @var bool|NULL */
private static $isLicenseKeyValid;
/**
* @return boolean
*/
public static function checkKey(): bool {
if (static::$isLicenseKeyValid === NULL) {
static::$isLicenseKeyValid = FALSE;
$configuration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::EXTENSION_KEY], [FALSE]);
if (isset($configuration['key']) && $key = trim($configuration['key'])) {
static::$isLicenseKeyValid = (bool) preg_match('/^([A-Z\d]{6}-?){4}$/', $key);
}
}
return static::$isLicenseKeyValid;
}
/**
* Licensing Service ping
*
* @param boolean $returnUrl
* @return string
*/
public static function ping($returnUrl = FALSE): string {
try {
$configuration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::EXTENSION_KEY], [FALSE]);
$key = '';
if (isset($configuration['key'])) {
$key = trim($configuration['key']);
}
$params = [
'extension' => self::EXTENSION_KEY,
'host' => GeneralUtility::getIndpEnv('HTTP_HOST'),
'key' => $key
];
$params = http_build_query($params);
$pingUrl = self::URL;
$pingUrl .= $params !== '' ? (strpos($pingUrl, '?') === FALSE ? '?' : '&') . $params : '';
if ($returnUrl) {
return $pingUrl;
}
GeneralUtility::getUrl($pingUrl);
} catch (\Exception $exception) {
}
return '';
}
/**
* Generates a random password string based on the configured password policies.
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @return ResponseInterface
* @throws \InvalidArgumentException
*/
public function ajaxPing(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
/** @var BackendUserAuthentication $backendUser */
$backendUser = $GLOBALS['BE_USER'];
if ($backendUser && !$backendUser->getModuleData('tools_beuser/index.php/web_SgNewsNews_pinged', 'ses')) {
$backendUser->pushModuleData('tools_beuser/index.php/web_SgNewsNews_pinged', TRUE);
self::ping();
}
return $response;
}
}
...@@ -32,6 +32,17 @@ use TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendViewHelper; ...@@ -32,6 +32,17 @@ use TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendViewHelper;
* Abstract view helper * Abstract view helper
*/ */
class AbstractViewHelper extends AbstractBackendViewHelper { class AbstractViewHelper extends AbstractBackendViewHelper {
/**
* @var boolean
*/
protected $escapeOutput = FALSE;
/**
* @var boolean
*/
protected $escapeChildren = FALSE;
/** /**
* Returns the base url of the site * Returns the base url of the site
* *
......
...@@ -26,10 +26,15 @@ namespace SGalinski\SgNews\ViewHelpers\Backend; ...@@ -26,10 +26,15 @@ namespace SGalinski\SgNews\ViewHelpers\Backend;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use SGalinski\SgNews\Service\LicensingService;
use SGalinski\SgNews\ViewHelpers\AbstractViewHelper;
use TYPO3\CMS\Backend\Clipboard\Clipboard;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList; use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
/** /**
...@@ -41,17 +46,49 @@ class ControlViewHelper extends AbstractViewHelper { ...@@ -41,17 +46,49 @@ class ControlViewHelper extends AbstractViewHelper {
* *
* @param string $table * @param string $table
* @param mixed $row * @param mixed $row
* @param array $sortingData
* @param boolean $clipboard
* @return string * @return string
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
*/ */
public function render($table, $row) { public function render($table, $row, array $sortingData = [], $clipboard = FALSE) {
if (!is_array($row)) {
$row = BackendUtility::getRecord($table, $row->getUid());
}
/** @var DatabaseRecordList $databaseRecordList */ /** @var DatabaseRecordList $databaseRecordList */
$databaseRecordList = GeneralUtility::makeInstance(DatabaseRecordList::class); $databaseRecordList = GeneralUtility::makeInstance(DatabaseRecordList::class);
/** @var BackendUserAuthentication $backendUser */ /** @var BackendUserAuthentication $backendUser */
$backendUser = $GLOBALS['BE_USER']; $backendUser = $GLOBALS['BE_USER'];
$pageInfo = BackendUtility::readPageAccess($row['pid'], $backendUser->getPagePermsClause(1)); $pageInfo = BackendUtility::readPageAccess($row['pid'], $backendUser->getPagePermsClause(1));
$databaseRecordList->calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo); $databaseRecordList->calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
return $databaseRecordList->makeControl($table, $row); $databaseRecordList->currentTable = $sortingData;
$out = $databaseRecordList->makeControl($table, $row);
if ($clipboard) {
$databaseRecordList->MOD_SETTINGS['clipBoard'] = TRUE;
$databaseRecordList->clipObj = GeneralUtility::makeInstance(Clipboard::class);
$databaseRecordList->clipObj->initializeClipboard();
$GLOBALS['SOBE'] = $databaseRecordList;
$out .= $databaseRecordList->makeClip($table, $row);
}
if ($table === 'pages' && LicensingService::checkKey()) {
$rootline = BackendUtility::BEgetRootLine($row['uid'], '', TRUE);
ksort($rootline);
$path = '/root';
foreach ($rootline as $page) {
$path .= '/p' . dechex($page['uid']);
}
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$out .= ' <div class="btn-group" role="group">
<a href="#" onclick="return sgNewsGoToPageModule(' . $row['uid'] . ', \'' . $path . '\');" class="btn btn-default" title="' . LocalizationUtility::translate(
'backend.button.editPageContent', 'SgNews'
) . '">' .
$iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() .
'</a>
</div>';
}
return $out;
} }
} }
...@@ -26,8 +26,9 @@ namespace SGalinski\SgNews\ViewHelpers\Backend; ...@@ -26,8 +26,9 @@ namespace SGalinski\SgNews\ViewHelpers\Backend;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use SGalinski\SgNews\Service\BackendNewsUtility;
use SGalinski\SgNews\ViewHelpers\AbstractViewHelper;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
/** /**
* Class EditOnClickViewHelper * Class EditOnClickViewHelper
...@@ -39,9 +40,17 @@ class EditOnClickViewHelper extends AbstractViewHelper { ...@@ -39,9 +40,17 @@ class EditOnClickViewHelper extends AbstractViewHelper {
* @param string $table * @param string $table
* @param int $uid * @param int $uid
* @param boolean $new * @param boolean $new
* @param string $type
* @return string * @return string
*/ */
public function render($table, $uid, $new = FALSE) { public function render($table, $uid, $new = FALSE, $type = '') {
return BackendUtility::editOnClick('&edit[' . $table . '][' . $uid . ']=' . ($new ? 'new' : 'edit'), '', -1); $additionalParameters = '';
if ($new && $table === 'pages' && in_array($type, ['news', 'category'], TRUE)) {
$additionalParameters = '&overrideVals[pages][doktype]=' .
($type === 'news' ? BackendNewsUtility::NEWS_DOKTYPE : BackendNewsUtility::CATEGORY_DOKTYPE);
}
return BackendUtility::editOnClick(
'&edit[' . $table . '][' . $uid . ']=' . ($new ? 'new' : 'edit') . $additionalParameters, '', -1
);
} }
} }
...@@ -26,36 +26,41 @@ namespace SGalinski\SgNews\ViewHelpers\Backend; ...@@ -26,36 +26,41 @@ namespace SGalinski\SgNews\ViewHelpers\Backend;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use TYPO3\CMS\Backend\Utility\BackendUtility; use SGalinski\SgNews\ViewHelpers\AbstractViewHelper;
use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
/** /**
* Class IconViewHelper * Class IconViewHelper
**/ **/
class IconViewHelper extends AbstractViewHelper { class IconViewHelper extends AbstractViewHelper {
/** /**
* Renders the icon for the specified record * Renders the icon for the specified identifier
* with the requested size option and overlay.
* *
* @param string $table * @param string $id
* @param mixed $row * @param string $size
* @param boolean $clickMenu * @param string $overlayId
* @return string * @return string
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function render($table, $row, $clickMenu = TRUE) { public function render($id, $size = '', $overlayId = NULL) {
$id = trim($id);
$size = trim($size);
switch ($size) {
case 'small' :
$size = Icon::SIZE_SMALL;
break;
case 'large' :
$size = Icon::SIZE_LARGE;
break;
default :
$size = Icon::SIZE_DEFAULT;
break;
}
/** @var IconFactory $iconFactory */ /** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class); $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$toolTip = BackendUtility::getRecordToolTip($row, $table); return $iconFactory->getIcon($id, $size, $overlayId)->render();
$iconImg = '<span ' . $toolTip . '>'
. $iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render()
. '</span>';
if ($clickMenu) {
return BackendUtility::wrapClickMenuOnIcon($iconImg, $table, $row['uid']);
} else {
return $iconImg;
}
} }
} }
<?php
namespace SGalinski\SgNews\ViewHelpers\Backend;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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 General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SGalinski\SgNews\ViewHelpers\AbstractViewHelper;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Class IconViewHelper
**/
class RecordIconViewHelper extends AbstractViewHelper {
/**
* Renders the icon for the specified record
*
* @param string $table
* @param mixed $row
* @param boolean $clickMenu
* @return string
* @throws \InvalidArgumentException
*/
public function render($table, $row, $clickMenu = TRUE) {
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$toolTip = BackendUtility::getRecordToolTip($row, $table);
$iconImg = '<span ' . $toolTip . '>'
. $iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render()
. '</span>';
if ($clickMenu) {
return BackendUtility::wrapClickMenuOnIcon($iconImg, $table, $row['uid']);
} else {
return $iconImg;
}
}
}
<?php
namespace SGalinski\SgNews\ViewHelpers\Backend;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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 General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SGalinski\SgNews\Service\BackendNewsUtility;
use SGalinski\SgNews\Service\LicensingService;
use SGalinski\SgNews\ViewHelpers\AbstractViewHelper;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Class EditOnClickViewHelper
**/
class TranslationLinksViewHelper extends AbstractViewHelper {
/**
* Renders the translation links for the specified record
*
* @param int $pageUid
* @param string $table
* @param int $uid
* @return string
* @throws \InvalidArgumentException
*/
public function render($pageUid = 0, $table, $uid) {
$out = '';
if (!LicensingService::checkKey()) {
return $out;
}
$table = trim($table);
if ($table !== 'pages' && !isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])) {
return $out;
}
$uid = (int) $uid;
$pageUid = (int) $pageUid;
$row = BackendUtility::getRecord($table, $uid);
if ($row) {
/** @var IconFactory $iconFactory */
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$lanuageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
$currentLanguageUid = $table === 'pages' ? 0 : $row[$lanuageField];
if ($currentLanguageUid === -1) {
$languages = [
-1 => [
'title' => LocalizationUtility::translate('backend.language.allLanguages', 'SgNews'),
'flag' => 'flags-multiple'
]
];
} else {
$languages = BackendNewsUtility::getAvailableLanguages($pageUid);
}
$editLabel = LocalizationUtility::translate('backend.action.edit', 'SgNews');
$newLabel = LocalizationUtility::translate('backend.action.new', 'SgNews');
$translationParameters = '&cmd[' . $table . '][' . $row['uid'] . '][localize]=%s';
if ($table === 'pages') {
$translationParameters = '&edit[pages_language_overlay][' . $row['uid'] . ']=new';
$translationParameters .= '&overrideVals[pages_language_overlay][doktype]=' . $row['doktype'];
$translationParameters .= '&overrideVals[pages_language_overlay][sys_language_uid]=%s';
}
foreach ($languages as $languageUid => $language) {
$translatedUid = 0;
if ((int) $languageUid <= 0) {
$translationTable = $table;
$translatedUid = $uid;
} else {
$translationTable = $table === 'pages' ? 'pages_language_overlay' : $table;
$translatedRows = BackendUtility::getRecordLocalization($table, $uid, $languageUid);
if (count($translatedRows)) {
$translatedUid = (int) $translatedRows[0]['uid'];
}
}
if ($translatedUid) {
$onClick = BackendUtility::editOnClick(
'&edit[' . $translationTable . '][' . $translatedUid . ']=edit', '', -1
);
$out .= ' <a href="#" onclick="' . $onClick . '" title="' . $language['title'] . ' [' . $editLabel . ']" >' .
$iconFactory->getIcon($language['flag'], Icon::SIZE_SMALL)->render() .
'</a>';
} else {
if ($table === 'pages') {
$onClick = BackendUtility::editOnClick(
sprintf($translationParameters, $languageUid), '', -1
);
} else {
$onClick = 'window.location.href=' . BackendUtility::getLinkToDataHandlerAction(
sprintf($translationParameters, $languageUid), -1
) . ' return false;';
}
$out .= ' <a href="#" onclick="' . $onClick . '" title="' . $language['title'] . ' [' . $newLabel . ']" >' .
$iconFactory->getIcon($language['flag'], Icon::SIZE_SMALL, 'overlay-new')->render() .
'</a>';
}
}
}
return $out;
}
}
<?php <?php
namespace SGalinski\SgNews\ViewHelpers\Backend\Widget; namespace SGalinski\SgNews\ViewHelpers\Backend\Widget;
/*************************************************************** /***************************************************************
...@@ -24,7 +25,7 @@ namespace SGalinski\SgNews\ViewHelpers\Backend\Widget; ...@@ -24,7 +25,7 @@ namespace SGalinski\SgNews\ViewHelpers\Backend\Widget;
* *
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use SGalinski\SgRoutes\ViewHelpers\Backend\Widget\Controller\PaginateController; use SGalinski\SgNews\ViewHelpers\Backend\Widget\Controller\PaginateController;
use TYPO3\CMS\Fluid\Core\Widget\AbstractWidgetViewHelper; use TYPO3\CMS\Fluid\Core\Widget\AbstractWidgetViewHelper;
/** /**
...@@ -32,7 +33,7 @@ use TYPO3\CMS\Fluid\Core\Widget\AbstractWidgetViewHelper; ...@@ -32,7 +33,7 @@ use TYPO3\CMS\Fluid\Core\Widget\AbstractWidgetViewHelper;
*/ */
class PaginateViewHelper extends AbstractWidgetViewHelper { class PaginateViewHelper extends AbstractWidgetViewHelper {
/** /**
* @var \SGalinski\SgRoutes\ViewHelpers\Backend\Widget\Controller\PaginateController * @var \SGalinski\SgNews\ViewHelpers\Backend\Widget\Controller\PaginateController
*/ */
protected $controller; protected $controller;
...@@ -54,6 +55,7 @@ class PaginateViewHelper extends AbstractWidgetViewHelper { ...@@ -54,6 +55,7 @@ class PaginateViewHelper extends AbstractWidgetViewHelper {
* @param string $as * @param string $as
* @param array $configuration * @param array $configuration
* @return string * @return string
* @throws \TYPO3\CMS\Fluid\Core\Widget\Exception\MissingControllerException
*/ */
public function render( public function render(
$objects, $as, $objects, $as,
......
<?php
return [
'sg_news::ajaxPing' => [
'path' => '/sg_news/ajaxPing',
'target' => SGalinski\SgNews\Service\LicensingService::class . '::ajaxPing',
]
];
...@@ -9,6 +9,74 @@ ...@@ -9,6 +9,74 @@
<authorEmail>stefan@sgalinski.de</authorEmail> <authorEmail>stefan@sgalinski.de</authorEmail>
</header> </header>
<body> <body>
<trans-unit id="backend.action.edit" approved="yes">
<source>Edit</source>
<target>Bearbeiten</target>
</trans-unit>
<trans-unit id="backend.action.new" approved="yes">
<source>New</source>
<target>Neu</target>
</trans-unit>
<trans-unit id="backend.button.createCategory" approved="yes">
<source>Add News Category</source>
<target>Kategorie hinzufügen</target>
</trans-unit>
<trans-unit id="backend.button.createNews" approved="yes">
<source>Add News Record</source>
<target>News-Eintrag hinzufügen</target>
</trans-unit>
<trans-unit id="backend.button.editPageContent" approved="yes">
<source>Edit Page Content</source>
<target>Seiteninhalt bearbeiten</target>
</trans-unit>
<trans-unit id="backend.filter" approved="yes">
<source>Filter</source>
<target>Filter</target>
</trans-unit>
<trans-unit id="backend.filters.categories" approved="yes">
<source>Categories</source>
<target>Kategorien</target>
</trans-unit>
<trans-unit id="backend.filters.categories.description" approved="yes">
<source>(Use the &lt;em&gt;ctrl&lt;/em&gt; or &lt;em&gt;cmd&lt;/em&gt; keys to deselect an option or to select multiple options)</source>
<target>(Benutze die &lt;em&gt;ctrl&lt;/em&gt; oder &lt;em&gt;cmd&lt;/em&gt;-Tasten, um eine Option abzuwählen oder mehrere anzuwählen)</target>
</trans-unit>
<trans-unit id="backend.filters.search" approved="yes">
<source>Search</source>
<target>Suche</target>
</trans-unit>
<trans-unit id="backend.filters.tags" approved="yes">
<source>Tags</source>
<target>Tags</target>
</trans-unit>
<trans-unit id="backend.filters.tags.description" approved="yes">
<source>(Use the &lt;em&gt;ctrl&lt;/em&gt; or &lt;em&gt;cmd&lt;/em&gt; keys to deselect an option or to select multiple options)</source>
<target>(Benutze die &lt;em&gt;ctrl&lt;/em&gt; oder &lt;em&gt;cmd&lt;/em&gt;-Tasten, um eine Option abzuwählen oder mehrere anzuwählen)</target>
</trans-unit>
<trans-unit id="backend.language.default" approved="yes">
<source>Default</source>
<target>Default</target>
</trans-unit>
<trans-unit id="backend.licenceBannerText" approved="yes">
<source><![CDATA[Please visit our <a class="text-primary" href="https://shop.sgalinski.de/products/" target="_blank">online shop</a> to purchase a license with all features and to remove this ad. You can enter the licence key in the extension manager configuration of sg_news. Thanks for helping our development efforts!]]></source>
<target><![CDATA[Bitte besuchen Sie unseren <a class="text-primary" href="https://shop.sgalinski.de/products/" target="_blank">Online-Shop</a>, um eine lizenzierte Version ohne diese Ad und mit allen Features zu erwerben. Sie können den Key in der Extension-Manager-Konfiguration von sg_news eingeben. Vielen Dank für die Unterstützung unserer Produkt-Entwicklungen!]]></target>
</trans-unit>
<trans-unit id="backend.licenceBannerTitle" approved="yes">
<source>SG_NEWS Free version</source>
<target>SG_NEWS Kostenlose Version</target>
</trans-unit>
<trans-unit id="backend.selectPage" approved="yes">
<source>Please select one of the Root or News Category pages to continue</source>
<target>Bitte wählen Sie eine der Root- oder News-Kategorieseiten aus, um fortzufahren</target>
</trans-unit>
<trans-unit id="backend.selectPageOr" approved="yes">
<source>, or</source>
<target>, oder</target>
</trans-unit>
<trans-unit id="configuration.licenseKey" approved="yes">
<source>License Key</source>
<target>Lizenzschlüssel</target>
</trans-unit>
<trans-unit id="frontend.overview.allTabLabel" approved="yes"> <trans-unit id="frontend.overview.allTabLabel" approved="yes">
<source>All</source> <source>All</source>
<target>Alle</target> <target>Alle</target>
......
...@@ -9,6 +9,57 @@ ...@@ -9,6 +9,57 @@
<authorEmail>stefan@sgalinski.de</authorEmail> <authorEmail>stefan@sgalinski.de</authorEmail>
</header> </header>
<body> <body>
<trans-unit id="backend.action.edit">
<source>Edit</source>
</trans-unit>
<trans-unit id="backend.action.new">
<source>New</source>
</trans-unit>
<trans-unit id="backend.button.createCategory">
<source>Add News Category</source>
</trans-unit>
<trans-unit id="backend.button.createNews">
<source>Add News Record</source>
</trans-unit>
<trans-unit id="backend.button.editPageContent">
<source>Edit Page Content</source>
</trans-unit>
<trans-unit id="backend.filter">
<source>Filter</source>
</trans-unit>
<trans-unit id="backend.filters.categories">
<source>Categories</source>
</trans-unit>
<trans-unit id="backend.filters.categories.description">
<source>(Use the &lt;em&gt;ctrl&lt;/em&gt; or &lt;em&gt;cmd&lt;/em&gt; keys to deselect an option or to select multiple options)</source>
</trans-unit>
<trans-unit id="backend.filters.search">
<source>Search</source>
</trans-unit>
<trans-unit id="backend.filters.tags">
<source>Tags</source>
</trans-unit>
<trans-unit id="backend.filters.tags.description">
<source>(Use the &lt;em&gt;ctrl&lt;/em&gt; or &lt;em&gt;cmd&lt;/em&gt; keys to deselect an option or to select multiple options)</source>
</trans-unit>
<trans-unit id="backend.language.default">
<source>Default</source>
</trans-unit>
<trans-unit id="backend.licenceBannerText">
<source><![CDATA[Please visit our <a class="text-primary" href="https://shop.sgalinski.de/products/" target="_blank">online shop</a> to purchase a license with all features and to remove this ad. You can enter the licence key in the extension manager configuration of sg_news. Thanks for helping our development efforts!]]></source>
</trans-unit>
<trans-unit id="backend.licenceBannerTitle">
<source>SG_NEWS Free version</source>
</trans-unit>
<trans-unit id="backend.selectPage">
<source>Please select one of the Root or News Category pages to continue</source>
</trans-unit>
<trans-unit id="backend.selectPageOr">
<source>, or</source>
</trans-unit>
<trans-unit id="configuration.licenseKey">
<source>License Key</source>
</trans-unit>
<trans-unit id="frontend.overview.allTabLabel"> <trans-unit id="frontend.overview.allTabLabel">
<source>All</source> <source>All</source>
</trans-unit> </trans-unit>
......
...@@ -8,13 +8,17 @@ ...@@ -8,13 +8,17 @@
2: 'TYPO3/CMS/Backend/Tooltip'}"> 2: 'TYPO3/CMS/Backend/Tooltip'}">
<sg:addJavaScriptFile javaScriptFile="{f:uri.resource(path: 'Scripts/Backend.js')}" /> <sg:addJavaScriptFile javaScriptFile="{f:uri.resource(path: 'Scripts/Backend.js')}" />
<f:format.raw>
<sg:inlineLanguageLabels labels="backend.delete_route, backend.delete_all, backend.htaccess" />
</f:format.raw>
<div class="module" data-module-id="" data-module-name=""> <div class="module" data-module-id="" data-module-name="">
<div class="module-docheader t3js-module-docheader"> <div class="module-docheader t3js-module-docheader">
<div class="module-docheader-bar module-docheader-bar-navigation t3js-module-docheader-bar t3js-module-docheader-bar-navigation"> <div class="module-docheader-bar module-docheader-bar-navigation t3js-module-docheader-bar t3js-module-docheader-bar-navigation">
<div class="module-docheader-bar-column-left"> <div class="module-docheader-bar-column-left">
<f:for each="{docHeader.menus}" as="menu">
<f:be.menus.actionMenu additionalAttributes="{name: menu.identifier}">
<f:for each="{menu.menuItems}" as="menuItem">
<option value="{menuItem.href}" {f:if(condition: '{menuItem.active}', then: 'selected="selected"')}>{menuItem.title}</option>
</f:for>
</f:be.menus.actionMenu>
</f:for>
</div> </div>
<div class="module-docheader-bar-column-right"> <div class="module-docheader-bar-column-right">
<span class="typo3-docheader-pagePath"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.path" />: <f:format.raw>{docHeader.metaInformation.path}</f:format.raw></span> <span class="typo3-docheader-pagePath"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.path" />: <f:format.raw>{docHeader.metaInformation.path}</f:format.raw></span>
...@@ -23,9 +27,6 @@ ...@@ -23,9 +27,6 @@
</div> </div>
<div class="module-docheader-bar module-docheader-bar-buttons t3js-module-docheader-bar t3js-module-docheader-bar-buttons"> <div class="module-docheader-bar module-docheader-bar-buttons t3js-module-docheader-bar t3js-module-docheader-bar-buttons">
<div class="module-docheader-bar-column-left"> <div class="module-docheader-bar-column-left">
<div class="btn-toolbar" role="toolbar" aria-label="">
<f:render section="iconButtons" />
</div>
</div> </div>
<div class="module-docheader-bar-column-right"> <div class="module-docheader-bar-column-right">
<f:render partial="ButtonBar" arguments="{buttons:docHeader.buttons.right}" /> <f:render partial="ButtonBar" arguments="{buttons:docHeader.buttons.right}" />
...@@ -35,6 +36,9 @@ ...@@ -35,6 +36,9 @@
</div> </div>
<div id="typo3-docbody"> <div id="typo3-docbody">
<div id="typo3-inner-docbody"> <div id="typo3-inner-docbody">
<f:if condition="{showLicenseBanner}">
<f:render partial="Backend/LicenseBanner" />
</f:if>
<h1> <h1>
<f:render section="headline" /> <f:render section="headline" />
</h1> </h1>
......
<f:form action="index" controller="Backend" method="post" objectName="filters" object="{filters}"> <f:form action="index" controller="Backend" method="post" objectName="filters" object="{filters}">
<div class="row"> <div class="row">
<div class="col-xs-5"> <f:if condition="{showCategoryFilter}">
<div class="form-group"> <div class="col-xs-6">
<label for="filter-category"> <div class="form-group">
<f:translate key="backend.filters.category" /> <label for="filter-categories">
<small><f:translate key="backend.filters.categories.description" /></small> <f:translate key="backend.filters.categories" />
</label> </label>
<f:form.select class="form-control" multiple="1" size="4" property="filters.categories" options="{categories}" id="filter-category" /> <f:form.select class="form-control" multiple="1" size="4" property="categories" options="{categories}" id="filter-categories" />
<small><f:format.raw><f:translate key="backend.filters.categories.description" /></f:format.raw></small>
</div>
</div> </div>
</div> </f:if>
<div class="col-xs-4"> <div class="col-xs-{f:if(condition: showCategoryFilter, then: '6', else: '5')}">
<div class="form-group"> <div class="form-group">
<label for="filter-search"><f:translate key="backend.filters.search" /></label> <label for="filter-tags">
<f:form.textfield class="form-control" property="search" id="filter-search" /> <f:translate key="backend.filters.tags" />
</label>
<f:form.select class="form-control" multiple="1" size="4" property="tags" options="{tags}" id="filter-tags" />
<small><f:format.raw><f:translate key="backend.filters.tags.description" /></f:format.raw></small>
</div> </div>
</div> </div>
<div class="col-xs-3"> <div class="col-xs-7 pull-right">
<div class="form-group"> <div class="row">
<label>&nbsp;</label><br /> <div class="col-xs-7">
<f:form.button class="btn btn-success form-group col-xs-12" type="submit">Filter</f:form.button> <div class="form-group">
<label for="filter-search"><f:translate key="backend.filters.search" /></label>
<f:form.textfield class="form-control" property="search" id="filter-search" />
</div>
</div>
<div class="col-xs-5">
<div class="form-group">
<label>&nbsp;</label><br />
<f:form.button class="btn btn-success form-group col-xs-12" type="submit"><f:translate key="backend.filter" /></f:form.button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
......
<div class="bannerContainer">
<div class="row">
<div class="col-xs-12">
<div class="panel panel-info">
<div class="panel-heading">
<f:image width="150" height="33" src="{f:uri.resource(path: 'Images/logo.svg')}" />
| <span><f:translate key="backend.licenceBannerTitle"/></span>
</div>
<div class="panel-body">
<f:format.raw>
<f:translate key="backend.licenceBannerText"/>
</f:format.raw>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
{namespace sg=SGalinski\SgNews\ViewHelpers}
<p>
<f:translate key="backend.selectPage" /><f:if condition="{pageUid}"><f:else>.</f:else></f:if>
<f:if condition="{pageUid}">
<f:translate key="backend.selectPageOr" />
<a href="#" class="btn btn-default" onclick="{sg:backend.editOnClick(table: 'pages', uid: pageUid, new: 1, type: 'category')}">
<sg:backend.icon id="actions-document-new" size="small" />
<f:translate key="backend.button.createCategory" />
</a>
</f:if>
</p>
<f:if condition="{pages}">
<div class="panel panel-default recordlist">
<div class="table-fit">
<table data-table="pages" class="table table-striped table-hover">
<tbody>
<f:for each="{pages}" as="page">
<tr data-uid="{page.uid}">
<td nowrap="nowrap" class="col-title">
<a href="#" onclick="sgNewsGoToPage({page.uid}, '{page.path}'); return false;">
<sg:backend.recordIcon table="pages" row="{page}" clickMenu="0" /> {page._thePathFull}
</a>
</td>
</tr>
</f:for>
</tbody>
</table>
</div>
</div>
</f:if>
\ No newline at end of file
{namespace sg=SGalinski\SgRoutes\ViewHelpers} {namespace sg=SGalinski\SgNews\ViewHelpers}
<f:layout name="Backend" /> <f:layout name="Backend" />
...@@ -11,45 +11,72 @@ ...@@ -11,45 +11,72 @@
<p> <p>
<f:translate key="backend.message" /> <f:translate key="backend.message" />
</p> </p>
<f:render partial="Backend/Filter" arguments="{categories: categories, filters: filters}" /> <f:if condition="{showNewsList}">
<f:then>
<f:render partial="Backend/Filter" arguments="{categories: categories, tags: tags, showCategoryFilter: showCategoryFilter, filters: filters}" />
<f:if condition="{news}">
<div class="panel panel-default recordlist"> <div class="form-group">
<div class="table-fit"> <f:if condition="{showCategoryFilter}">
<table data-table="pages" class="table table-striped table-hover"> <f:then>
<sg:backend.widget.paginate objects="{news}" as="paginatedNews" configuration="{insertAbove: 1, itemsPerPage: 20}"> <a href="#" class="btn btn-default" onclick="{sg:backend.editOnClick(table: 'pages', uid: pageUid, new: 1, type: 'category')}">
<tbody> <sg:backend.icon id="actions-document-new" size="small" />
<f:for each="{paginatedNews}" as="singleNews"> <f:translate key="backend.button.createCategory" />
{sg:backend.editOnClick(table: 'pages', uid: singleNews.uid) -> sg:set(name: 'editOnClick')} </a>
<tr data-uid="{singleNews.uid}"> </f:then>
<td nowrap="nowrap" class="col-icon"> <f:else>
<f:format.raw> <a href="#" class="btn btn-default" onclick="{sg:backend.editOnClick(table: 'pages', uid: pageUid, new: 1, type: 'news')}">
<sg:backend.icon table="pages" row="{singleNews}" /> <sg:backend.icon id="actions-document-new" size="small" />
</f:format.raw> <f:translate key="backend.button.createNews" />
</td> </a>
<td nowrap="nowrap"> </f:else>
<a href="#" onclick="{editOnClick}"> </f:if>
<span>
<f:if condition="{singleNews.title}">
<f:then>
{singleNews.title}
</f:then>
</f:if>
</span>
</a>
</td>
<td nowrap="nowrap" class="col-control">
<f:format.raw>
<sg:backend.control table="tx_sgroutes_domain_model_page" row="{singleNews}" />
</f:format.raw>
</td>
</tr>
</f:for>
</tbody>
</sg:backend.widget.paginate>
</table>
</div> </div>
</div>
</f:if>
<f:if condition="{news}">
<div class="panel panel-default recordlist">
<div class="table-fit">
<table data-table="pages" class="table table-striped table-hover">
<sg:backend.widget.paginate objects="{news}" as="paginatedNews" configuration="{insertAbove: 1, itemsPerPage: 20}">
<tbody>
<f:for each="{paginatedNews}" as="singleNews">
<tr data-uid="{singleNews.uid}">
<td nowrap="nowrap" class="col-icon">
<f:format.raw>
<sg:backend.recordIcon table="pages" row="{singleNews}" />
</f:format.raw>
</td>
<td nowrap="nowrap">
<a href="#" onclick="{sg:backend.editOnClick(table: 'pages', uid: singleNews.uid)}">
<span>
<f:if condition="{singleNews.translation_title}">
<f:then>
{singleNews.translation_title}
</f:then>
<f:else>
{singleNews.title}
</f:else>
</f:if>
</span>
</a> <br />
<sg:backend.translationLinks pageUid="{pageUid}" table="pages" uid="{singleNews.uid}" />
</td>
<td nowrap="nowrap" class="col-control">
<f:format.raw>
<sg:backend.control table="pages" row="{singleNews}" clipboard="1" />
</f:format.raw>
</td>
</tr>
</f:for>
</tbody>
</sg:backend.widget.paginate>
</table>
</div>
</div>
</f:if>
</f:then>
<f:else>
<f:render partial="Backend/SelectPage" arguments="{pages: alternativePageOptions, pageUid: pageUid}" />
</f:else>
</f:if>
</f:section> </f:section>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment