Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions

View File

@@ -0,0 +1,649 @@
<?php
namespace GeorgRinger\News\Controller;
use GeorgRinger\News\Backend\RecordList\NewsDatabaseRecordList;
use GeorgRinger\News\Domain\Model\Dto\AdministrationDemand;
use GeorgRinger\News\Domain\Repository\AdministrationRepository;
use GeorgRinger\News\Domain\Repository\CategoryRepository;
use GeorgRinger\News\Domain\Repository\NewsRepository;
use GeorgRinger\News\Domain\Repository\TagRepository;
use GeorgRinger\News\Event\AdministrationExtendMenuEvent;
use GeorgRinger\News\Event\AdministrationIndexActionEvent;
use GeorgRinger\News\Event\AdministrationNewsPidListingActionEvent;
use GeorgRinger\News\Utility\Page;
use TYPO3\CMS\Backend\Clipboard\Clipboard;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\Menu\Menu;
use TYPO3\CMS\Backend\Utility\BackendUtility as BackendUtilityCore;
use TYPO3\CMS\Backend\View\BackendTemplateView;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* Administration controller
*/
class AdministrationController extends NewsController
{
/** @var int */
protected $pageUid = 0;
/** @var array */
protected $tsConfiguration = [];
/**
* @var \GeorgRinger\News\Domain\Repository\AdministrationRepository
*/
protected $administrationRepository;
/** @var ConfigurationManagerInterface */
protected $configurationManager;
/** @var array */
protected $pageInformation = [];
/** @var array */
protected $allowedNewTables = [];
/** @var array */
protected $deniedNewTables = [];
public function initializeAction()
{
$this->pageUid = (int)GeneralUtility::_GET('id');
$this->pageInformation = BackendUtilityCore::readPageAccess($this->pageUid, '');
$this->setTsConfig();
parent::initializeAction();
}
/** @var BackendTemplateView */
protected $view;
/** @var IconFactory */
protected $iconFactory;
/** @var string */
protected $defaultViewObjectName = BackendTemplateView::class;
/**
* AdministrationController constructor.
* @param NewsRepository $newsRepository
* @param CategoryRepository $categoryRepository
* @param TagRepository $tagRepository
* @param ConfigurationManagerInterface $configurationManager
* @param AdministrationRepository $administrationRepository
* @param AdministrationRepository $iconFactory
*/
public function __construct(
NewsRepository $newsRepository,
CategoryRepository $categoryRepository,
TagRepository $tagRepository,
ConfigurationManagerInterface $configurationManager,
AdministrationRepository $administrationRepository,
IconFactory $iconFactory
) {
parent::__construct($newsRepository, $categoryRepository, $tagRepository, $configurationManager);
$this->administrationRepository = $administrationRepository;
$this->iconFactory = $iconFactory;
}
/**
* @param ViewInterface $view
*/
protected function initializeView(ViewInterface $view)
{
parent::initializeView($view);
$view->getModuleTemplate()->getDocHeaderComponent()->setMetaInformation([]);
$pageRenderer = $this->view->getModuleTemplate()->getPageRenderer();
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DateTimePicker');
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/AjaxDataHandler');
$pageRenderer->loadRequireJsModule('TYPO3/CMS/News/AdministrationModule');
$pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf');
$dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
$pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
$web_list_modTSconfig = BackendUtilityCore::getPagesTSconfig($this->pageUid)['mod.']['web_list.'] ?? [];
$this->allowedNewTables = GeneralUtility::trimExplode(
',',
$web_list_modTSconfig['allowedNewTables'] ?? '',
true
);
$this->deniedNewTables = GeneralUtility::trimExplode(
',',
$web_list_modTSconfig['deniedNewTables'] ?? '',
true
);
$this->createMenu();
$this->createButtons();
$view->assign('showSupportArea', $this->showSupportArea());
}
protected function createMenu(): void
{
$uriBuilder = $this->objectManager->get(UriBuilder::class);
$uriBuilder->setRequest($this->request);
$menu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
$menu->setIdentifier('news');
$actions = [
['action' => 'index', 'label' => 'newsListing'],
['action' => 'newsPidListing', 'label' => 'newsPidListing'],
['action' => 'donate', 'label' => 'donate']
];
foreach ($actions as $action) {
$item = $menu->makeMenuItem()
->setTitle($this->getLanguageService()->sL('LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:module.' . $action['label']))
->setHref($uriBuilder->uriFor($action['action'], [], 'Administration', 'News', 'web_NewsAdministration'))
->setActive($this->request->getControllerActionName() === $action['action']);
$menu->addMenuItem($item);
}
$menu = $this->extendMenu($menu);
if ($menu instanceof Menu) {
$this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
}
if (is_array($this->pageInformation)) {
$this->view->getModuleTemplate()->getDocHeaderComponent()->setMetaInformation($this->pageInformation);
}
}
/**
* Extends menu selector with items from 3rd party extensions.
*
* @param Menu $menu
* @return Menu
*/
protected function extendMenu(Menu $menu): Menu
{
$event = $this->eventDispatcher->dispatch(new AdministrationExtendMenuEvent($this, $menu));
return $event->getMenu();
}
protected function createButtons(): void
{
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
$uriBuilder = $this->objectManager->get(UriBuilder::class);
$uriBuilder->setRequest($this->request);
if ($this->request->getControllerActionName() === 'index' && $this->isFilteringEnabled()) {
$toggleButton = $buttonBar->makeLinkButton()
->setHref('#')
->setDataAttributes([
'togglelink' => '1',
'toggle' => 'tooltip',
'placement' => 'bottom',
])
->setTitle($this->getLanguageService()->sL('LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:administration.toggleForm'))
->setIcon($this->iconFactory->getIcon('actions-filter', Icon::SIZE_SMALL));
$buttonBar->addButton($toggleButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
}
$buttons = [
[
'table' => 'tx_news_domain_model_news',
'label' => 'module.createNewNewsRecord',
'action' => 'newNews',
'icon' => 'ext-news-type-default'
],
[
'table' => 'tx_news_domain_model_tag',
'label' => 'module.createNewTag',
'action' => 'newTag',
'icon' => 'ext-news-tag'
],
[
'table' => 'sys_category',
'label' => 'module.createNewCategory',
'action' => 'newCategory',
'icon' => 'mimetypes-x-sys_category'
]
];
foreach ($buttons as $key => $tableConfiguration) {
if ($this->showButton($tableConfiguration['table'])) {
$title = $this->getLanguageService()->sL('LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:' . $tableConfiguration['label']);
$viewButton = $buttonBar->makeLinkButton()
->setHref($uriBuilder->reset()->setRequest($this->request)->uriFor(
$tableConfiguration['action'],
[],
'Administration'
))
->setDataAttributes([
'toggle' => 'tooltip',
'placement' => 'bottom',
'title' => $title])
->setTitle($title)
->setIcon($this->iconFactory->getIcon($tableConfiguration['icon'], Icon::SIZE_SMALL, 'overlay-new'));
$buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
}
}
$clipBoard = GeneralUtility::makeInstance(Clipboard::class);
$clipBoard->initializeClipboard();
$elFromTable = $clipBoard->elFromTable('tx_news_domain_model_news');
if (!empty($elFromTable)) {
$viewButton = $buttonBar->makeLinkButton()
->setHref($clipBoard->pasteUrl('', $this->pageUid))
->setOnClick('return ' . $clipBoard->confirmMsgText(
'pages',
BackendUtilityCore::getRecord('pages', $this->pageUid),
'into',
$elFromTable
))
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_pasteInto'))
->setIcon($this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL));
$buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
}
// Donation
$donationButton = $buttonBar->makeLinkButton()
->setHref($uriBuilder->reset()->setRequest($this->request)->uriFor(
'donate',
[],
'Administration'
))
->setTitle($this->getLanguageService()->sL('LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:administration.donation.title'))
->setIcon($this->iconFactory->getIcon('ext-news-donation', Icon::SIZE_SMALL));
$buttonBar->addButton($donationButton, ButtonBar::BUTTON_POSITION_RIGHT);
// Refresh
$refreshButton = $buttonBar->makeLinkButton()
->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
$buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT);
// Shortcut
if ($this->getBackendUser()->mayMakeShortcut()) {
$shortcutButton = $buttonBar->makeShortcutButton()
->setGetVariables(['route', 'module', 'id'])
->setModuleName($this->request->getPluginName())
->setDisplayName('Shortcut');
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
}
}
protected function showButton(string $table): bool
{
if (!$this->getBackendUser()->check('tables_modify', $table)) {
return false;
}
// No deny/allow tables are set:
if (empty($this->allowedNewTables) && empty($this->deniedNewTables)) {
return true;
}
$showButton = !in_array($table, $this->deniedNewTables, true) &&
(empty($this->allowedNewTables) || in_array($table, $this->allowedNewTables, true));
return $showButton;
}
public function indexAction(): void
{
$this->redirectToPageOnStart();
$demandVars = GeneralUtility::_GET('tx_news_web_newsadministration');
$demand = GeneralUtility::makeInstance(AdministrationDemand::class);
$autoSubmitForm = 0;
if (!empty($demandVars['demand'] ?? [])) {
foreach ($demandVars['demand'] as $key => $value) {
if (property_exists(AdministrationDemand::class, $key)) {
$getter = 'set' . ucfirst($key);
$demand->$getter($value);
}
}
} else {
// Preselect by TsConfig (e.g. tx_news.module.preselect.topNewsRestriction = 1)
if (isset($this->tsConfiguration['preselect.'])
&& is_array($this->tsConfiguration['preselect.'])
) {
$anyPropertySet = false;
unset($this->tsConfiguration['preselect.']['orderByAllowed']);
foreach ($this->tsConfiguration['preselect.'] as $propertyName => $propertyValue) {
$propertySet = ObjectAccess::setProperty($demand, $propertyName, $propertyValue);
if ($propertySet) {
$anyPropertySet = true;
}
}
if ($anyPropertySet && !GeneralUtility::_GET('formSubmitted')) {
$autoSubmitForm = 1;
}
}
if (!(bool)($this->tsConfiguration['alwaysShowFilter'] ?? false) || !$this->isFilteringEnabled()) {
$this->view->assign('hideForm', true);
}
}
$this->view->assign('autoSubmitForm', $autoSubmitForm);
$categories = $this->categoryRepository->findParentCategoriesByPid($this->pageUid);
$idList = [];
foreach ($categories as $c) {
$idList[] = $c->getUid();
}
if (empty($idList) && !$this->getBackendUser()->isAdmin()) {
$idList = $this->getBackendUser()->getCategoryMountPoints();
}
if (!empty($this->tsConfiguration['allowedCategoryRootIds'])) {
$allowedList = GeneralUtility::intExplode(',', $this->tsConfiguration['allowedCategoryRootIds'], true);
if (!empty($allowedList)) {
$idList = array_intersect($idList, $allowedList);
}
}
// Initialize the dblist object:
$dblist = GeneralUtility::makeInstance(NewsDatabaseRecordList::class);
$this->view->getModuleTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ActionDispatcher');
$this->view->getModuleTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
$pageinfo = BackendUtilityCore::readPageAccess($this->pageUid, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
$dblist->pageRow = $pageinfo;
$majorVersion = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion();
if ($majorVersion <= 10) {
$dblist->calcPerms = Permission::CONTENT_EDIT;
} else {
$dblist->calcPerms->set(Permission::CONTENT_EDIT);
if ($majorVersion >= 11) {
$dblist->displayColumnSelector = false;
$dblist->displayRecordDownload = false;
}
}
$dblist->disableSingleTableView = true;
$dblist->clickTitleMode = 'edit';
$dblist->allFields = 1;
$dblist->displayFields = false;
$dblist->dontShowClipControlPanels = true;
$pointer = MathUtility::forceIntegerInRange(GeneralUtility::_GP('pointer'), 0);
$limit = isset($this->settings['list']['paginate']['itemsPerPage']) ? (int)$this->settings['list']['paginate']['itemsPerPage'] : 20;
$dblist->start(
$this->pageUid,
'tx_news_domain_model_news',
$pointer,
'',
$demand->getRecursive(),
$limit
);
$dblist->setDispFields();
$dblist->noControlPanels = !(bool)($this->tsConfiguration['controlPanels'] ?? false);
$dblist->setFields = [
'tx_news_domain_model_news' => GeneralUtility::trimExplode(',', $this->tsConfiguration['columns'] ?? 'teaser,istopnews,datetime,categories', true)
];
$tableRendering = $dblist->generateList();
if (!$tableRendering && GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() <= 10) {
$tableRendering = $dblist->HTMLcode;
}
$tableRendering = trim($tableRendering);
$counter = !empty($tableRendering);
$this->view->getModuleTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Recordlist/Recordlist');
$assignedValues = [
'moduleToken' => $this->getToken(true),
'page' => $this->pageUid,
'demand' => $demand,
'news' => $tableRendering,
'newsCount' => $counter,
'showSearchForm' => (!is_null($demand) || $counter > 0),
'requestUri' => GeneralUtility::quoteJSvalue(rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'))),
'categories' => $this->categoryRepository->findTree($idList),
'filters' => $this->tsConfiguration['filters.'],
'enableFiltering' => $this->isFilteringEnabled(),
'dateformat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy']
];
$event = $this->eventDispatcher->dispatch(new AdministrationIndexActionEvent($this, $assignedValues));
$this->view->assignMultiple($event->getAssignedValues());
}
/**
* Shows a page tree including count of news + category records
*/
public function newsPidListingAction(int $treeLevel = 2): void
{
$tree = Page::pageTree($this->pageUid, $treeLevel);
$rawTree = [];
foreach ($tree->tree as $row) {
$this->countRecordsOnPage($row);
$rawTree[] = $row;
}
$event = $this->eventDispatcher->dispatch(new AdministrationNewsPidListingActionEvent($this, $rawTree, $treeLevel));
$this->view->assignMultiple([
'tree' => $event->getRawTree(),
'treeLevel' => $event->getTreeLevel(),
]);
}
public function donateAction(): void
{
$this->view->assignMultiple([
'counts' => $this->administrationRepository->getTotalCounts()
]);
}
/**
* Redirect to form to create a news record
*
* @return void
*/
public function newNewsAction(): void
{
$this->redirectToCreateNewRecord('tx_news_domain_model_news');
}
/**
* Redirect to form to create a category record
*
* @return void
*/
public function newCategoryAction(): void
{
$this->redirectToCreateNewRecord('sys_category');
}
/**
* Redirect to form to create a tag record
*
* @return void
*/
public function newTagAction(): void
{
$this->redirectToCreateNewRecord('tx_news_domain_model_tag');
}
/**
* Update page record array with count of news & category records
*
* @param array $row page record
*
* @return void
*/
private function countRecordsOnPage(array &$row): void
{
$pageUid = (int)$row['row']['uid'];
$counts = [
'countNews' => 'tx_news_domain_model_news',
'countCategories' => 'sys_category',
'countTags' => 'tx_news_domain_model_tag',
];
foreach ($counts as $key => $table) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$row[$key] = $queryBuilder->count('*')
->from($table)
->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageUid, \PDO::PARAM_INT)))
->execute()
->fetchColumn(0);
}
$row['countAll'] = ($row['countNews'] + $row['countCategories'] + $row['countTags']);
}
/**
* Redirect to creating a new record
*
* @param string $table table name
*/
private function redirectToCreateNewRecord(string $table): void
{
$pid = $this->pageUid;
if ($pid === 0 && isset($this->tsConfiguration['defaultPid.'])
&& is_array($this->tsConfiguration['defaultPid.'])
&& isset($this->tsConfiguration['defaultPid.'][$table])
) {
$pid = (int)$this->tsConfiguration['defaultPid.'][$table];
}
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
$returnUrl = $uriBuilder->buildUriFromRoutePath('/module/web/NewsAdministration/', [
'id' => $this->pageUid,
'token' => $this->getToken(true)
]);
$params = [
'edit[' . $table . '][' . $pid . ']' => 'new',
'returnUrl' => (string)$returnUrl
];
$url = $uriBuilder->buildUriFromRoute('record_edit', $params);
HttpUtility::redirect((string)$url);
}
/**
* Set the TsConfig configuration for the extension
*/
protected function setTsConfig(): void
{
$tsConfig = BackendUtilityCore::getPagesTSconfig($this->pageUid);
if (isset($tsConfig['tx_news.']['module.']) && is_array($tsConfig['tx_news.']['module.'])) {
$this->tsConfiguration = $tsConfig['tx_news.']['module.'];
}
}
/**
* Check if at least one filter is enabled
*/
protected function isFilteringEnabled(): bool
{
if (isset($this->tsConfiguration['filters.'])) {
foreach ($this->tsConfiguration['filters.'] as $filter => $enabled) {
if ($enabled == 1) {
// Check dependencies on other filter
if (($filter === 'categoryConjunction' || $filter === 'includeSubCategories')
&& $this->tsConfiguration['filters.']['categories'] == 0) {
continue;
}
return true;
}
}
}
return false;
}
/**
* If defined in TsConfig with tx_news.module.redirectToPageOnStart = 123
* and the current page id is 0, a redirect to the given page will be done
*/
protected function redirectToPageOnStart(): void
{
$allowedPage = (int)($this->tsConfiguration['allowedPage'] ?? 0);
$redirectPageOnStart = (int)($this->tsConfiguration['redirectToPageOnStart'] ?? 0);
if ($allowedPage > 0 && $this->pageUid !== $allowedPage) {
$id = $allowedPage;
} elseif ($this->pageUid === 0 && $redirectPageOnStart > 0) {
$id = $redirectPageOnStart;
}
if (!empty($id)) {
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
$url = $uriBuilder->buildUriFromRoutePath('/module/web/NewsAdministration/', [
'id' => $id,
'token' => $this->getToken(true)
]);
HttpUtility::redirect((string)$url);
}
}
/**
* Get a CSRF token
*
* @param bool $tokenOnly Set it to TRUE to get only the token, otherwise including the &moduleToken= as prefix
*/
protected function getToken(bool $tokenOnly = false): string
{
$tokenParameterName = 'token';
$token = FormProtectionFactory::get('backend')->generateToken('route', 'web_NewsAdministration');
if ($tokenOnly) {
return $token;
}
return '&' . $tokenParameterName . '=' . $token;
}
/**
* Show support area only for admins in given percent of time
*
* @param int $probabilityInPercent
*/
private function showSupportArea(int $probabilityInPercent = 10): bool
{
if (!$this->getBackendUser()->isAdmin()) {
return false;
}
if (mt_rand() % 100 <= $probabilityInPercent) {
return true;
}
return false;
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
}