From 185fe7a5c5b703ea80f4e8f12449071c1bb7f4d4 Mon Sep 17 00:00:00 2001 From: Sebastian Zoglowek Date: Thu, 3 Aug 2023 12:47:58 +0200 Subject: [PATCH] Finish simultaneous contao 4.13 and 5.1 support --- README.md | 2 +- composer.json | 10 +- contao/classes/Glossary.php | 153 ++----- contao/config/config.php | 3 - contao/dca/tl_glossary.php | 258 +---------- contao/dca/tl_glossary_item.php | 421 +----------------- .../DataContainer/GlossaryItemListener.php | 2 - .../DataContainer/GlossaryListener.php | 2 +- src/EventListener/SitemapListener.php | 6 +- 9 files changed, 76 insertions(+), 781 deletions(-) diff --git a/README.md b/README.md index 625111b..178bc8a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ --- -> Working with **Contao 4.9** and up to **Contao 4.13** (PHP ^7.4 and PHP 8) +> Working with **Contao 4.13** and **Contao 5.1** (PHP ^8.1) --- diff --git a/composer.json b/composer.json index 2c86749..c92520a 100644 --- a/composer.json +++ b/composer.json @@ -4,13 +4,13 @@ "description": "A glossary extension for the Contao Open Source CMS. Glossaries are organized in archives similar to news and events and can be displayed via a list and reader module.", "license": "AGPL-3.0-or-later", "authors": [ - { - "name": "Fabian Ekert", - "homepage": "https://github.com/eki89" - }, { "name": "Sebastian Zoglowek", "homepage": "https://github.com/zoglo" + }, + { + "name": "Fabian Ekert", + "homepage": "https://github.com/eki89" } ], "require": { @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.1.x-dev" + "dev-main": "2.2.x-dev" }, "contao-manager-plugin": "Oveleon\\ContaoGlossaryBundle\\ContaoManager\\Plugin" }, diff --git a/contao/classes/Glossary.php b/contao/classes/Glossary.php index 57c1188..4ba37a1 100644 --- a/contao/classes/Glossary.php +++ b/contao/classes/Glossary.php @@ -25,8 +25,8 @@ use Contao\PageModel; use Contao\StringUtil; use Contao\System; +use Contao\Validator; use Oveleon\ContaoGlossaryBundle\Model\GlossaryItemModel; -use Oveleon\ContaoGlossaryBundle\Model\GlossaryModel; use function Symfony\Component\String\u; /** @@ -41,95 +41,6 @@ class Glossary extends Frontend */ private static array $arrUrlCache = []; - /** - * Add glossary items to the indexer. - */ - public function getSearchablePages(array $arrPages, $intRoot = 0, bool $blnIsSitemap = false): array - { - $arrRoot = []; - - if ($intRoot > 0) - { - $arrRoot = $this->Database->getChildRecords($intRoot, PageModel::getTable()); - } - - $arrProcessed = []; - $time = time(); - - // Get all glossaries - $objGlossary = GlossaryModel::findByProtected(''); - - // Walk through each glossary - if (null !== $objGlossary) - { - while ($objGlossary->next()) - { - // Skip glossaries without target page - if (!$objGlossary->jumpTo) - { - continue; - } - - // Skip glossaries outside the root nodes - if (!empty($arrRoot) && !\in_array($objGlossary->jumpTo, $arrRoot)) - { - continue; - } - - // Get the URL of the jumpTo page - if (!isset($arrProcessed[$objGlossary->jumpTo])) - { - $objParent = PageModel::findWithDetails($objGlossary->jumpTo); - - // The target page does not exist - if (null === $objParent) - { - continue; - } - - // The target page has not been published (see #5520) - if (!$objParent->published || ($objParent->start && $objParent->start > $time) || ($objParent->stop && $objParent->stop <= $time)) - { - continue; - } - - if ($blnIsSitemap) - { - // The target page is protected (see #8416) - if ($objParent->protected) - { - continue; - } - - // The target page is exempt from the sitemap (see #6418) - if ('noindex,nofollow' === $objParent->robots) - { - continue; - } - } - - // Generate the URL - $arrProcessed[$objGlossary->jumpTo] = $objParent->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' : '/items/%s'); - } - - $strUrl = $arrProcessed[$objGlossary->jumpTo]; - - // Get the items - $objArticle = GlossaryItemModel::findPublishedDefaultByPid($objGlossary->id); - - if (null !== $objArticle) - { - while ($objArticle->next()) - { - $arrPages[] = $this->getLink($objArticle, $strUrl); - } - } - } - } - - return $arrPages; - } - /** * Generate a URL and return it as string. */ @@ -156,7 +67,14 @@ public static function generateUrl(GlossaryItemModel $objItem, bool $blnAbsolute } else { - self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand($objItem->url); + $url = $objItem->url; + + if (Validator::isRelativeUrl($url)) + { + $url = Environment::get('path') . '/' . $url; + } + + self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand($url); } break; @@ -188,7 +106,7 @@ public static function generateUrl(GlossaryItemModel $objItem, bool $blnAbsolute if (!$objPage instanceof PageModel) { - self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand(Environment::get('request')); + self::$arrUrlCache[$strCacheKey] = StringUtil::ampersand(Environment::get('requestUri')); } else { @@ -256,8 +174,8 @@ public static function parseGlossaryItem(GlossaryItemModel $objGlossaryItem, str $objTemplate->teaser = StringUtil::encodeEmail($objTemplate->teaser); // Replace insert tags within teaser when fetching items via controller (see #13) - // ToDo: rewrite - //$objTemplate->teaser = Controller::replaceInsertTags($objTemplate->teaser); + $parser = System::getContainer()->get('contao.insert_tag.parser'); + $objTemplate->teaser = $parser->replace((string) $objTemplate->teaser); } // Display the "read more" button for external/article links @@ -293,15 +211,16 @@ public static function parseGlossaryItem(GlossaryItemModel $objGlossaryItem, str $objTemplate->addImage = false; // Add an image - if ($objGlossaryItem->addImage && $objGlossaryItem->singleSRC) + if ($objGlossaryItem->addImage) { $objModel = FilesModel::findByUuid($objGlossaryItem->singleSRC); - if (null !== $objModel && is_file(System::getContainer()->getParameter('kernel.project_dir') . 'Glossary.php/' .$objModel->path)) + if (null !== $objModel) { // Do not override the field now that we have a model registry $arrGlossaryItem = $objGlossaryItem->row(); + // ToDo: Move method into src // Override the default image size if ($imgSize) { @@ -309,33 +228,37 @@ public static function parseGlossaryItem(GlossaryItemModel $objGlossaryItem, str if ($size[0] > 0 || $size[1] > 0 || is_numeric($size[2]) || ($size[2][0] ?? null) === '_') { - $arrGlossaryItem['size'] = $imgSize; + $imgSize = $imgSize; } } - $arrGlossaryItem['singleSRC'] = $objModel->path; - Controller::addImageToTemplate($objTemplate, $arrGlossaryItem, null, null, $objModel); + $figureBuilder = System::getContainer() + ->get('contao.image.studio') + ->createFigureBuilder() + ->from($objModel->path) + ->setSize($imgSize) + ->enableLightbox((bool) $objGlossaryItem->fullsize); - // Link to the glossary item if no image link has been defined - if (!$objTemplate->fullsize && !$objTemplate->imageUrl) + // If the external link is opened in a new window, open the image link in a new window as well (see #210) + if ('external' === $objTemplate->source && $objTemplate->target) { - // Load language for 'read more' link - System::loadLanguageFile('default'); - - // Unset the image title attribute - $picture = $objTemplate->picture; - unset($picture['title']); - $objTemplate->picture = $picture; - - // Link to the glossary item - $objTemplate->href = $objTemplate->link; - $objTemplate->linkTitle = StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['readMore'], $objGlossaryItem->keyword), true); + $figureBuilder->setLinkAttribute('target', '_blank'); + } - // If the external link is opened in a new window, open the image link in a new window, too - if ('external' === $objTemplate->source && $objTemplate->target && false === strpos($objTemplate->attributes, 'target="_blank"')) + if (null !== ($figure = $figureBuilder->buildIfResourceExists())) + { + // ToDo: intCount (see contao #5708/#5851). + if (!$figure->getLinkHref()) { - $objTemplate->attributes .= ' target="_blank"'; + $linkTitle = StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['readMore'], $objGlossaryItem->keyword), true); + + $figure = $figureBuilder + ->setLinkHref($objTemplate->link) + ->setLinkAttribute('title', $linkTitle) + ->build(); } + + $figure->applyLegacyTemplateData($objTemplate, $objGlossaryItem->imagemargin, $objGlossaryItem->floating); } } } diff --git a/contao/config/config.php b/contao/config/config.php index c77549c..57b8fa0 100644 --- a/contao/config/config.php +++ b/contao/config/config.php @@ -35,9 +35,6 @@ $GLOBALS['TL_MODELS']['tl_glossary'] = GlossaryModel::class; $GLOBALS['TL_MODELS']['tl_glossary_item'] = GlossaryItemModel::class; -// Register hooks -$GLOBALS['TL_HOOKS']['getSearchablePages'][] = ['Oveleon\ContaoGlossaryBundle\Glossary', 'getSearchablePages']; - // Add permissions $GLOBALS['TL_PERMISSIONS'][] = 'glossarys'; $GLOBALS['TL_PERMISSIONS'][] = 'glossaryp'; diff --git a/contao/dca/tl_glossary.php b/contao/dca/tl_glossary.php index c2ef51a..3a859e4 100644 --- a/contao/dca/tl_glossary.php +++ b/contao/dca/tl_glossary.php @@ -12,21 +12,12 @@ * @copyright Oveleon */ -use Contao\Backend; use Contao\BackendUser; use Contao\Controller; -use Contao\CoreBundle\Exception\AccessDeniedException; -use Contao\CoreBundle\Security\ContaoCorePermissions; use Contao\DataContainer; use Contao\DC_Table; -use Contao\Image; -use Contao\Input; -use Contao\PageModel; -use Contao\StringUtil; use Contao\System; -use Oveleon\ContaoGlossaryBundle\Security\ContaoGlossaryPermissions; -use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Oveleon\ContaoGlossaryBundle\EventListener\DataContainer\GlossaryListener; $GLOBALS['TL_DCA']['tl_glossary'] = [ // Config @@ -36,17 +27,14 @@ 'switchToEdit' => true, 'enableVersioning' => true, 'markAsCopy' => 'title', - 'onload_callback' => [ - ['tl_glossary', 'checkPermission'], - ], 'oncreate_callback' => [ - ['tl_glossary', 'adjustPermissions'], + [GlossaryListener::class, 'adjustPermissions'], ], 'oncopy_callback' => [ - ['tl_glossary', 'adjustPermissions'], + [GlossaryListener::class, 'adjustPermissions'], ], 'oninvalidate_cache_tags_callback' => [ - ['tl_glossary', 'addSitemapCacheInvalidationTag'], + [GlossaryListener::class, 'addSitemapCacheInvalidationTag'], ], 'sql' => [ 'keys' => [ @@ -83,18 +71,18 @@ 'editheader' => [ 'href' => 'act=edit', 'icon' => 'header.svg', - 'button_callback' => ['tl_glossary', 'editHeader'], + 'button_callback' => [GlossaryListener::class, 'editHeader'], ], 'copy' => [ 'href' => 'act=copy', 'icon' => 'copy.svg', - 'button_callback' => ['tl_glossary', 'copyArchive'], + 'button_callback' => [GlossaryListener::class, 'copyArchive'], ], 'delete' => [ 'href' => 'act=delete', 'icon' => 'delete.svg', 'attributes' => 'onclick="if(!confirm(\''.($GLOBALS['TL_LANG']['MSC']['deleteConfirm'] ?? null).'\'))return false;Backend.getScrollOffset()"', - 'button_callback' => ['tl_glossary', 'deleteArchive'], + 'button_callback' => [GlossaryListener::class, 'deleteArchive'], ], 'show' => [ 'href' => 'act=show', @@ -155,7 +143,7 @@ 'inputType' => 'imageSize', 'reference' => &$GLOBALS['TL_LANG']['MSC'], 'eval' => ['rgxp' => 'natural', 'includeBlankOption' => true, 'nospace' => true, 'helpwizard' => true, 'tl_class' => 'w50'], - 'options_callback' => static fn () => System::getContainer()->get('contao.image.image_sizes')->getOptionsForUser(BackendUser::getInstance()), + 'options_callback' => static fn () => System::getContainer()->get('contao.image.sizes')->getOptionsForUser(BackendUser::getInstance()), 'sql' => "varchar(64) NOT NULL default ''", ], 'protected' => [ @@ -177,233 +165,3 @@ ], ], ]; - -/** - * Provide miscellaneous methods that are used by the data configuration array. - * - * @author Fabian Ekert - */ -class tl_glossary extends Backend -{ - /** - * Import the back end user object. - */ - public function __construct() - { - parent::__construct(); - $this->import(BackendUser::class, 'User'); - } - - /** - * Check permissions to edit table tl_glossary. - * - * @throws AccessDeniedException - */ - public function checkPermission(): void - { - if ($this->User->isAdmin) - { - return; - } - - // Set root IDs - if (empty($this->User->glossarys) || !is_array($this->User->glossarys)) - { - $root = [0]; - } - else - { - $root = $this->User->glossarys; - } - - $GLOBALS['TL_DCA']['tl_glossary']['list']['sorting']['root'] = $root; - - // Check permissions to add archives - if (!$this->User->hasAccess('create', 'glossaryp')) - { - $GLOBALS['TL_DCA']['tl_glossary']['config']['closed'] = true; - } - - /** @var SessionInterface $objSession */ - $objSession = System::getContainer()->get('session'); - - // Check current action - switch (Input::get('act')) - { - case 'select': - // Allow - break; - - case 'create': - if (!$this->User->hasAccess('create', 'glossaryp')) - { - throw new AccessDeniedException('Not enough permissions to create glossaries.'); - } - break; - - case 'edit': - case 'copy': - case 'delete': - case 'show': - if (!in_array(Input::get('id'), $root) || ('delete' === Input::get('act') && !$this->User->hasAccess('delete', 'glossaryp'))) - { - throw new AccessDeniedException('Not enough permissions to '.Input::get('act').' glossary ID '.Input::get('id').'.'); - } - break; - - case 'editAll': - case 'deleteAll': - case 'overrideAll': - case 'copyAll': - $session = $objSession->all(); - - if ('deleteAll' === Input::get('act') && !$this->User->hasAccess('delete', 'glossaryp')) - { - $session['CURRENT']['IDS'] = []; - } - else - { - $session['CURRENT']['IDS'] = array_intersect((array) $session['CURRENT']['IDS'], $root); - } - $objSession->replace($session); - break; - - default: - if (Input::get('act')) - { - throw new AccessDeniedException('Not enough permissions to '.Input::get('act').' glossary.'); - } - break; - } - } - - /** - * Add the glossary to the permissions. - * - * @param $insertId - */ - public function adjustPermissions($insertId): void - { - // The oncreate_callback passes $insertId as second argument - if (4 === func_num_args()) - { - $insertId = func_get_arg(1); - } - - if ($this->User->isAdmin) - { - return; - } - - // Set root IDs - if (empty($this->User->glossarys) || !is_array($this->User->glossarys)) - { - $root = [0]; - } - else - { - $root = $this->User->glossarys; - } - - // The glossary is enabled already - if (in_array($insertId, $root)) - { - return; - } - - /** @var AttributeBagInterface $objSessionBag */ - $objSessionBag = System::getContainer()->get('session')->getBag('contao_backend'); - - $arrNew = $objSessionBag->get('new_records'); - - if (is_array($arrNew['tl_glossary']) && in_array($insertId, $arrNew['tl_glossary'])) - { - // Add the permissions on group level - if ('custom' !== $this->User->inherit) - { - $objGroup = $this->Database->execute('SELECT id, glossarys, glossaryp FROM tl_user_group WHERE id IN('.implode(',', array_map('\intval', $this->User->groups)).')'); - - while ($objGroup->next()) - { - $arrGlossaryp = StringUtil::deserialize($objGroup->glossaryp); - - if (is_array($arrGlossaryp) && in_array('create', $arrGlossaryp)) - { - $arrGlossarys = StringUtil::deserialize($objGroup->glossarys, true); - $arrGlossarys[] = $insertId; - - $this->Database->prepare('UPDATE tl_user_group SET glossarys=? WHERE id=?') - ->execute(serialize($arrGlossarys), $objGroup->id) - ; - } - } - } - - // Add the permissions on user level - if ('group' !== $this->User->inherit) - { - $objUser = $this->Database->prepare('SELECT glossarys, glossaryp FROM tl_user WHERE id=?') - ->limit(1) - ->execute($this->User->id) - ; - - $arrGlossaryp = StringUtil::deserialize($objUser->glossaryp); - - if (is_array($arrGlossaryp) && in_array('create', $arrGlossaryp)) - { - $arrGlossarys = StringUtil::deserialize($objUser->glossarys, true); - $arrGlossarys[] = $insertId; - - $this->Database->prepare('UPDATE tl_user SET glossarys=? WHERE id=?') - ->execute(serialize($arrGlossarys), $this->User->id) - ; - } - } - - // Add the new element to the user object - $root[] = $insertId; - $this->User->glossarys = $root; - } - } - - /** - * Return the edit header button. - */ - public function editHeader(array $row, string $href, string $label, string $title, string $icon, string $attributes): string - { - return System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELDS_OF_TABLE, 'tl_glossary') ? '' . Image::getHtml($icon, $label) . ' ' : Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)) . ' '; - } - - /** - * Return the copy archive button. - */ - public function copyArchive(array $row, string $href, string $label, string $title, string $icon, string $attributes): string - { - return System::getContainer()->get('security.helper')->isGranted(ContaoGlossaryPermissions::USER_CAN_CREATE_ARCHIVES) ? '' . Image::getHtml($icon, $label) . ' ' : Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)) . ' '; - } - - /** - * Return the delete archive button. - */ - public function deleteArchive(array $row, string $href, string $label, string $title, string $icon, string $attributes): string - { - return System::getContainer()->get('security.helper')->isGranted(ContaoGlossaryPermissions::USER_CAN_DELETE_ARCHIVES) ? '' . Image::getHtml($icon, $label) . ' ' : Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)) . ' '; - } - - /** - * @param DataContainer $dc - * - * @return array - */ - public function addSitemapCacheInvalidationTag($dc, array $tags) - { - $pageModel = PageModel::findWithDetails($dc->activeRecord->jumpTo); - - if (null === $pageModel) - { - return $tags; - } - - return array_merge($tags, ['contao.sitemap.'.$pageModel->rootId]); - } -} diff --git a/contao/dca/tl_glossary_item.php b/contao/dca/tl_glossary_item.php index 0a49f13..4545237 100644 --- a/contao/dca/tl_glossary_item.php +++ b/contao/dca/tl_glossary_item.php @@ -12,23 +12,18 @@ * @copyright Oveleon */ -use Contao\Automator; use Contao\Backend; use Contao\BackendUser; use Contao\Config; -use Contao\CoreBundle\Exception\AccessDeniedException; use Contao\DataContainer; -use Contao\Image; -use Contao\Input; +use Contao\DC_Table; use Contao\LayoutModel; use Contao\PageModel; -use Contao\StringUtil; use Contao\System; -use Contao\Versions; +use Oveleon\ContaoGlossaryBundle\EventListener\DataContainer\GlossaryItemListener; use Oveleon\ContaoGlossaryBundle\Glossary; use Oveleon\ContaoGlossaryBundle\Model\GlossaryItemModel; use Oveleon\ContaoGlossaryBundle\Model\GlossaryModel; -use Symfony\Component\HttpFoundation\Session\SessionInterface; System::loadLanguageFile('tl_content'); @@ -42,21 +37,21 @@ 'enableVersioning' => true, 'markAsCopy' => 'keyword', 'onload_callback' => [ - ['tl_glossary_item', 'checkPermission'], - ['tl_glossary_item', 'generateSitemap'], + [GlossaryItemListener::class, 'checkPermission'], + [GlossaryItemListener::class, 'generateSitemap'], ], 'oncut_callback' => [ - ['tl_glossary_item', 'scheduleUpdate'], + [GlossaryItemListener::class, 'scheduleUpdate'], ], 'ondelete_callback' => [ - ['tl_glossary_item', 'scheduleUpdate'], + [GlossaryItemListener::class, 'scheduleUpdate'], ], 'onsubmit_callback' => [ - ['tl_glossary_item', 'setGlossaryItemGroup'], - ['tl_glossary_item', 'scheduleUpdate'], + [GlossaryItemListener::class, 'setGlossaryItemGroup'], + [GlossaryItemListener::class, 'scheduleUpdate'], ], 'oninvalidate_cache_tags_callback' => [ - ['tl_glossary_item', 'addSitemapCacheInvalidationTag'], + [GlossaryItemListener::class, 'addSitemapCacheInvalidationTag'], ], 'sql' => [ 'keys' => [ @@ -70,12 +65,12 @@ // List 'list' => [ 'sorting' => [ - 'mode' => 4, + 'mode' => DataContainer::MODE_PARENT, 'fields' => ['keyword'], 'headerFields' => ['title', 'jumpTo', 'tstamp', 'protected'], 'panelLayout' => 'filter;sort,search,limit', - 'child_record_callback' => ['tl_glossary_item', 'listGlossaryItems'], - 'child_record_class' => 'no_padding', + 'child_record_callback' => [GlossaryItemListener::class, 'listItems'], + 'child_record_class' => 'no_padding' ], 'global_operations' => [ 'all' => [ @@ -107,9 +102,8 @@ 'attributes' => 'onclick="if(!confirm(\''.($GLOBALS['TL_LANG']['MSC']['deleteConfirm'] ?? null).'\'))return false;Backend.getScrollOffset()"', ], 'toggle' => [ + 'href' => 'act=toggle&field=published', 'icon' => 'visible.svg', - 'attributes' => 'onclick="Backend.getScrollOffset();return AjaxRequest.toggleVisibility(this,%s)"', - 'button_callback' => ['tl_glossary_item', 'toggleIcon'], 'showInHeader' => true, ], 'show' => [ @@ -278,7 +272,7 @@ 'inputType' => 'imageSize', 'reference' => &$GLOBALS['TL_LANG']['MSC'], 'eval' => ['rgxp' => 'natural', 'includeBlankOption' => true, 'nospace' => true, 'helpwizard' => true, 'tl_class' => 'w50'], - 'options_callback' => static fn () => System::getContainer()->get('contao.image.image_sizes')->getOptionsForUser(BackendUser::getInstance()), + 'options_callback' => static fn () => System::getContainer()->get('contao.image.sizes')->getOptionsForUser(BackendUser::getInstance()), 'sql' => "varchar(64) NOT NULL default ''", ], 'imagemargin' => [ @@ -372,6 +366,7 @@ 'sql' => "varchar(255) NOT NULL default ''", ], 'published' => [ + 'toggle' => true, 'label' => &$GLOBALS['TL_LANG']['tl_glossary_item']['published'], 'exclude' => true, 'filter' => true, @@ -400,147 +395,6 @@ public function __construct() $this->import(BackendUser::class, 'User'); } - /** - * Check permissions to edit table tl_glossary_item. - * - * @throws AccessDeniedException - */ - public function checkPermission(): void - { - if ($this->User->isAdmin) - { - return; - } - - // Set the root IDs - if (empty($this->User->glossarys) || !is_array($this->User->glossarys)) - { - $root = [0]; - } - else - { - $root = $this->User->glossarys; - } - - $id = strlen(Input::get('id')) ? Input::get('id') : CURRENT_ID; - - // Check current action - switch (Input::get('act')) - { - case 'paste': - case 'select': - // Check CURRENT_ID here (see #247) - if (!in_array(CURRENT_ID, $root)) - { - throw new AccessDeniedException('Not enough permissions to access glossary ID '.$id.'.'); - } - break; - - case 'create': - if (!Input::get('pid') || !in_array(Input::get('pid'), $root)) - { - throw new AccessDeniedException('Not enough permissions to create glossary items in glossary ID '.Input::get('pid').'.'); - } - break; - - case 'cut': - case 'copy': - if ('cut' === Input::get('act') && 1 === (int) Input::get('mode')) - { - $objGlossary = $this->Database->prepare('SELECT pid FROM tl_glossary_item WHERE id=?') - ->limit(1) - ->execute(Input::get('pid')) - ; - - if ($objGlossary->numRows < 1) - { - throw new AccessDeniedException('Invalid glossary item ID '.Input::get('pid').'.'); - } - - $pid = $objGlossary->pid; - } - else - { - $pid = Input::get('pid'); - } - - if (!in_array($pid, $root)) - { - throw new AccessDeniedException('Not enough permissions to '.Input::get('act').' glossary item ID '.$id.' to glossary ID '.$pid.'.'); - } - // no break - - case 'edit': - case 'show': - case 'delete': - case 'toggle': - $objGlossary = $this->Database->prepare('SELECT pid FROM tl_glossary_item WHERE id=?') - ->limit(1) - ->execute($id) - ; - - if ($objGlossary->numRows < 1) - { - throw new AccessDeniedException('Invalid glossary item ID '.$id.'.'); - } - - if (!in_array($objGlossary->pid, $root)) - { - throw new AccessDeniedException('Not enough permissions to '.Input::get('act').' glossary item ID '.$id.' of glossary ID '.$objGlossary->pid.'.'); - } - break; - - case 'editAll': - case 'deleteAll': - case 'overrideAll': - case 'cutAll': - case 'copyAll': - if (!in_array($id, $root)) - { - throw new AccessDeniedException('Not enough permissions to access glossary ID '.$id.'.'); - } - - $objGlossary = $this->Database->prepare('SELECT id FROM tl_glossary_item WHERE pid=?') - ->execute($id) - ; - - /** @var SessionInterface $objSession */ - $objSession = System::getContainer()->get('session'); - - $session = $objSession->all(); - $session['CURRENT']['IDS'] = array_intersect((array) $session['CURRENT']['IDS'], $objGlossary->fetchEach('id')); - $objSession->replace($session); - break; - - default: - if (Input::get('act')) - { - throw new AccessDeniedException('Invalid command "'.Input::get('act').'".'); - } - - if (!in_array($id, $root)) - { - throw new AccessDeniedException('Not enough permissions to access glossary ID '.$id.'.'); - } - break; - } - } - - /** - * Set group by keyword. - */ - public function setGlossaryItemGroup(DataContainer $dc): void - { - $newGroup = mb_strtoupper(mb_substr($dc->activeRecord->keyword, 0, 1, 'UTF-8')); - - if ($dc->activeRecord->letter !== $newGroup) - { - $this->Database->prepare('UPDATE tl_glossary_item SET letter=? WHERE id=?') - ->execute($newGroup, $dc->id) - ; - } - } - /** * Auto-generate the glossary item alias if it has not been set yet. * @@ -552,7 +406,9 @@ public function setGlossaryItemGroup(DataContainer $dc): void */ public function generateAlias($varValue, DataContainer $dc) { - $aliasExists = fn (string $alias): bool => $this->Database->prepare('SELECT id FROM tl_glossary_item WHERE alias=? AND id!=?')->execute($alias, $dc->id)->numRows > 0; + $aliasExists = function (string $alias) use ($dc): bool { + return $this->Database->prepare("SELECT id FROM tl_glossary_item WHERE alias=? AND id!=?")->execute($alias, $dc->id)->numRows > 0; + }; // Generate alias if there is none if (!$varValue) @@ -628,64 +484,6 @@ static function ($strVal) { return $title; } - /** - * List a glossary item. - * - * @param array $arrRow - * - * @return string - */ - public function listGlossaryItems($arrRow) - { - return '
'.$arrRow['keyword'].'
'; - } - - /** - * Check for modified glossary items and update the XML files if necessary. - */ - public function generateSitemap(): void - { - /** @var SessionInterface $objSession */ - $objSession = System::getContainer()->get('session'); - - $session = $objSession->get('glossaryitems_updater'); - - if (empty($session) || !is_array($session)) - { - return; - } - - $this->import(Automator::class, 'Automator'); - $this->Automator->generateSitemap(); - - $objSession->set('glossaryitems_updater', null); - } - - /** - * Schedule a glossary item update. - * - * This method is triggered when a single glossary item or multiple glossary items - * are modified (edit/editAll), moved (cut/cutAll) or deleted (delete/deleteAll). - * Since duplicated items are unpublished by default, it is not necessary to - * schedule updates on copyAll as well. - */ - public function scheduleUpdate(DataContainer $dc): void - { - // Return if there is no ID - if (!$dc->activeRecord || !$dc->activeRecord->pid || 'copy' === Input::get('act')) - { - return; - } - - /** @var SessionInterface $objSession */ - $objSession = System::getContainer()->get('session'); - - // Store the ID in the session - $session = $objSession->get('glossaryitems_updater'); - $session[] = $dc->activeRecord->pid; - $objSession->set('glossaryitems_updater', array_unique($session)); - } - /** * Get all articles and return them as array. * @@ -713,13 +511,13 @@ public function getArticleAlias(DataContainer $dc) return $arrAlias; } - $objAlias = $this->Database->prepare('SELECT a.id, a.title, a.inColumn, p.title AS parent FROM tl_article a LEFT JOIN tl_page p ON p.id=a.pid WHERE a.pid IN('.implode(',', array_map('\intval', array_unique($arrPids))).') ORDER BY parent, a.sorting') + $objAlias = $this->Database->prepare("SELECT a.id, a.title, a.inColumn, p.title AS parent FROM tl_article a LEFT JOIN tl_page p ON p.id=a.pid WHERE a.pid IN('.implode(',', array_map('\intval', array_unique($arrPids))).') ORDER BY parent, a.sorting") ->execute($dc->id) ; } else { - $objAlias = $this->Database->prepare('SELECT a.id, a.title, a.inColumn, p.title AS parent FROM tl_article a LEFT JOIN tl_page p ON p.id=a.pid ORDER BY parent, a.sorting') + $objAlias = $this->Database->prepare("SELECT a.id, a.title, a.inColumn, p.title AS parent FROM tl_article a LEFT JOIN tl_page p ON p.id=a.pid ORDER BY parent, a.sorting") ->execute($dc->id) ; } @@ -778,183 +576,4 @@ public function getSourceOptions(DataContainer $dc) return $arrOptions; } - - /** - * Add a link to the list items import wizard. - * - * @return string - */ - public function listImportWizard() - { - return ' '.Image::getHtml('tablewizard.svg', $GLOBALS['TL_LANG']['MSC']['tw_import'][0]).''; - } - - /** - * Return the "toggle visibility" button. - * - * @param array $row - * @param string $href - * @param string $label - * @param string $title - * @param string $icon - * @param string $attributes - * - * @return string - */ - public function toggleIcon($row, $href, $label, $title, $icon, $attributes) - { - if (Input::get('tid')) - { - $this->toggleVisibility(Input::get('tid'), 1 === (int) Input::get('state'), (func_num_args() <= 12 ? null : func_get_arg(12))); - $this->redirect($this->getReferer()); - } - - // Check permissions AFTER checking the tid, so hacking attempts are logged - if (!$this->User->hasAccess('tl_glossary_item::published', 'alexf')) - { - return ''; - } - - $href .= '&tid='.$row['id'].'&state='.($row['published'] ? '' : 1); - - if (!$row['published']) - { - $icon = 'invisible.svg'; - } - - return ''.Image::getHtml($icon, $label, 'data-state="'.($row['published'] ? 1 : 0).'"').' '; - } - - /** - * Disable/enable a user group. - * - * @param int $intId - * @param bool $blnVisible - * @param DataContainer $dc - */ - public function toggleVisibility($intId, $blnVisible, DataContainer $dc = null): void - { - // Set the ID and action - Input::setGet('id', $intId); - Input::setGet('act', 'toggle'); - - if ($dc) - { - $dc->id = $intId; - } - - // Trigger the onload_callback - if (is_array($GLOBALS['TL_DCA']['tl_glossary_item']['config']['onload_callback'] ?? null)) - { - foreach ($GLOBALS['TL_DCA']['tl_glossary_item']['config']['onload_callback'] as $callback) - { - if (is_array($callback)) - { - $this->import($callback[0]); - $this->{$callback[0]}->{$callback[1]}($dc); - } - elseif (is_callable($callback)) - { - $callback($dc); - } - } - } - - // Check the field access - if (!$this->User->hasAccess('tl_glossary_item::published', 'alexf')) - { - throw new AccessDeniedException('Not enough permissions to publish/unpublish glossary item ID '.$intId.'.'); - } - - // Set the current record - $objRow = $this->Database->prepare('SELECT * FROM tl_glossary_item WHERE id=?') - ->limit(1) - ->execute($intId) - ; - - if ($objRow->numRows < 1) - { - throw new AccessDeniedException('Invalid glossary item ID '.$intId.'.'); - } - - if ($dc) - { - $dc->activeRecord = $objRow; - } - - $objVersions = new Versions('tl_glossary_item', $intId); - $objVersions->initialize(); - - // Trigger the save_callback - if (is_array($GLOBALS['TL_DCA']['tl_glossary_item']['fields']['published']['save_callback'] ?? null)) - { - foreach ($GLOBALS['TL_DCA']['tl_glossary_item']['fields']['published']['save_callback'] as $callback) - { - if (is_array($callback)) - { - $this->import($callback[0]); - $blnVisible = $this->{$callback[0]}->{$callback[1]}($blnVisible, $dc); - } - elseif (is_callable($callback)) - { - $blnVisible = $callback($blnVisible, $dc); - } - } - } - - $time = time(); - - // Update the database - $this->Database->prepare("UPDATE tl_glossary_item SET tstamp=$time, published='".($blnVisible ? '1' : '')."' WHERE id=?") - ->execute($intId) - ; - - if ($dc) - { - $dc->activeRecord->tstamp = $time; - $dc->activeRecord->published = ($blnVisible ? '1' : ''); - } - - // Trigger the onsubmit_callback - if (is_array($GLOBALS['TL_DCA']['tl_glossary_item']['config']['onsubmit_callback'] ?? null)) - { - foreach ($GLOBALS['TL_DCA']['tl_glossary_item']['config']['onsubmit_callback'] as $callback) - { - if (is_array($callback)) - { - $this->import($callback[0]); - $this->{$callback[0]}->{$callback[1]}($dc); - } - elseif (is_callable($callback)) - { - $callback($dc); - } - } - } - - $objVersions->create(); - - if ($dc) - { - $dc->invalidateCacheTags(); - } - } - - /** - * @param DataContainer $dc - * - * @return array - */ - public function addSitemapCacheInvalidationTag($dc, array $tags) - { - $archiveModel = GlossaryModel::findByPk($dc->activeRecord->pid); - $pageModel = PageModel::findWithDetails($archiveModel->jumpTo); - - if (null === $pageModel) - { - return $tags; - } - - return array_merge($tags, ['contao.sitemap.'.$pageModel->rootId]); - } } diff --git a/src/EventListener/DataContainer/GlossaryItemListener.php b/src/EventListener/DataContainer/GlossaryItemListener.php index 199e3e3..e5c1b50 100644 --- a/src/EventListener/DataContainer/GlossaryItemListener.php +++ b/src/EventListener/DataContainer/GlossaryItemListener.php @@ -3,13 +3,11 @@ namespace Oveleon\ContaoGlossaryBundle\EventListener\DataContainer; use Contao\Automator; -use Contao\Config; use Contao\Controller; use Contao\CoreBundle\Exception\AccessDeniedException; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\Database; use Contao\DataContainer; -use Contao\Date; use Contao\Input; use Contao\PageModel; use Contao\System; diff --git a/src/EventListener/DataContainer/GlossaryListener.php b/src/EventListener/DataContainer/GlossaryListener.php index 670f1bd..0c89bdf 100644 --- a/src/EventListener/DataContainer/GlossaryListener.php +++ b/src/EventListener/DataContainer/GlossaryListener.php @@ -135,7 +135,7 @@ public function adjustPermissions(int|string $insertId): void // Add the new element to the user object $root[] = $insertId; - $objUser->recommendations = $root; + $objUser->glossarys = $root; } } diff --git a/src/EventListener/SitemapListener.php b/src/EventListener/SitemapListener.php index ee2fd35..010dffe 100644 --- a/src/EventListener/SitemapListener.php +++ b/src/EventListener/SitemapListener.php @@ -53,16 +53,16 @@ public function __invoke(SitemapEvent $event): void return; } - // Walk through each recommendation archive + // Walk through each glossary foreach ($objGlossaries as $objGlossary) { - // Skip recommendation archives without target page + // Skip glossaries without target page if (!$objGlossary->jumpTo) { continue; } - // Skip recommendation archives outside the root nodes + // Skip glossaries outside the root nodes if (!\in_array($objGlossary->jumpTo, $arrRoot, true)) { continue;