├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── composer-dependency-analyser.php ├── composer.json ├── config └── services.yml ├── contao ├── config │ └── config.php ├── dca │ ├── tl_content.php │ ├── tl_module.php │ ├── tl_news.php │ ├── tl_news_archive.php │ ├── tl_news_category.php │ ├── tl_page.php │ ├── tl_settings.php │ ├── tl_user.php │ └── tl_user_group.php ├── languages │ ├── de │ │ ├── default.php │ │ ├── modules.php │ │ ├── tl_module.php │ │ ├── tl_news.php │ │ ├── tl_news_archive.php │ │ ├── tl_news_category.php │ │ ├── tl_page.php │ │ ├── tl_settings.php │ │ ├── tl_user.php │ │ └── tl_user_group.php │ ├── en │ │ ├── default.php │ │ ├── modules.php │ │ ├── tl_module.php │ │ ├── tl_news.php │ │ ├── tl_news_archive.php │ │ ├── tl_news_category.php │ │ ├── tl_page.php │ │ ├── tl_settings.php │ │ ├── tl_user.php │ │ └── tl_user_group.php │ ├── fr │ │ ├── default.php │ │ ├── modules.php │ │ ├── tl_module.php │ │ ├── tl_news.php │ │ ├── tl_news_archive.php │ │ ├── tl_news_category.php │ │ ├── tl_page.php │ │ ├── tl_user.php │ │ └── tl_user_group.php │ ├── it │ │ ├── default.php │ │ ├── modules.php │ │ ├── tl_module.php │ │ ├── tl_news.php │ │ ├── tl_news_archive.php │ │ ├── tl_news_category.php │ │ ├── tl_page.php │ │ └── tl_user.php │ └── pl │ │ ├── default.php │ │ ├── modules.php │ │ ├── tl_module.php │ │ ├── tl_news.php │ │ ├── tl_news_archive.php │ │ ├── tl_news_category.php │ │ ├── tl_page.php │ │ └── tl_user.php └── templates │ ├── modules │ ├── mod_newscategories.html5 │ ├── mod_newscategories_cumulative.html5 │ └── mod_newscategories_cumulativehierarchical.html5 │ └── navigation │ ├── nav_newscategories.html5 │ └── nav_newscategories_hierarchical.html5 ├── docs ├── README.md ├── configuration.md ├── frontend-modules.md ├── images │ ├── category-edit.png │ ├── category-language.png │ ├── category-list.png │ ├── cumulative-filter.png │ ├── feeds.png │ ├── frontend-module.png │ ├── manage.png │ ├── news-archive.png │ ├── news-edit.png │ └── page-settings.png ├── insert-tags.md ├── installation.md ├── multilingual-features.md ├── news-feeds.md └── template-adjustments.md ├── ecs.php ├── phpstan.neon ├── public └── icon.png └── src ├── CodefogNewsCategoriesBundle.php ├── ContaoManager └── Plugin.php ├── ContentElement └── NewsFilterElement.php ├── Criteria ├── NewsCriteria.php └── NewsCriteriaBuilder.php ├── DependencyInjection └── CodefogNewsCategoriesExtension.php ├── EventListener ├── ChangeLanguageListener.php ├── DataContainer │ ├── CategoryPermissionListener.php │ ├── CategoryRootListener.php │ ├── ContentNewsModuleOptionsListener.php │ ├── NewsArchiveOperationListener.php │ ├── NewsCategoriesOptionsListener.php │ ├── NewsCategoryAliasListener.php │ ├── NewsDefaultCategoriesListener.php │ └── SettingSlugOptionsListener.php ├── InsertTagsListener.php ├── NewsFeedListener.php ├── NewsListener.php └── TemplateListener.php ├── Exception ├── CategoryFilteringNotAppliedException.php ├── CategoryNotFoundException.php └── NoNewsException.php ├── FrontendModule ├── CumulativeFilterModule.php ├── CumulativeHierarchicalFilterModule.php ├── NewsArchiveModule.php ├── NewsCategoriesModule.php ├── NewsListModule.php ├── NewsMenuModule.php └── NewsModule.php ├── Model └── NewsCategoryModel.php ├── NewsCategoriesManager.php └── Security ├── NewsCategoriesPermissions.php └── Voter ├── BackendAccessVoter.php └── DataContainer └── NewsCategoriesVoter.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: codefog 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: ~ 5 | pull_request: ~ 6 | 7 | permissions: read-all 8 | 9 | jobs: 10 | ecs: 11 | name: build-tools 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup PHP 15 | uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: 8.1 18 | extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, mysqli, pcre, pdo_mysql, zlib 19 | coverage: none 20 | 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | 24 | - name: Install the dependencies 25 | run: | 26 | composer install --no-interaction --no-progress 27 | 28 | - name: Run all build-tools to validate code 29 | run: composer run build-tools 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.php_cs.cache 2 | /composer.lock 3 | /node_modules 4 | /vendor 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Codefog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # News Categories bundle for Contao CMS 2 | 3 | [![](https://img.shields.io/packagist/v/codefog/contao-news_categories.svg)](https://packagist.org/packages/codefog/contao-news_categories) 4 | [![](https://img.shields.io/packagist/l/codefog/contao-news_categories.svg)](https://github.com/codefog/contao-news_categories/blob/master/LICENSE.txt) 5 | [![](https://img.shields.io/packagist/dt/codefog/contao-news_categories.svg)](https://packagist.org/packages/codefog/contao-news_categories) 6 | 7 | News Categories is a bundle for the [Contao CMS](https://contao.org). 8 | 9 | Extend the Contao news module with categories which allows for better news organization in the frontend and backend. 10 | Every category has its own title, alias, front end title, description and other useful features. Additionally, each 11 | of them can be hidden from the public just like the news articles. The extension comes with adjustments to all news 12 | frontend modules and an extra content element which allows for even quicker frontend stuff setup. 13 | 14 | ![](docs/images/category-list.png) 15 | 16 | ## Documentation 17 | 18 | 1. [Installation](docs/installation.md) 19 | 2. [Configuration](docs/configuration.md) 20 | 3. [Frontend modules](docs/frontend-modules.md) 21 | 4. [Template adjustments](docs/template-adjustments.md) 22 | 5. [News feeds](docs/news-feeds.md) 23 | 6. [Multilingual features](docs/multilingual-features.md) 24 | 7. [Insert tags](docs/insert-tags.md) 25 | 26 | ## Copyright 27 | 28 | This project has been created and is maintained by [Codefog](https://codefog.pl). 29 | -------------------------------------------------------------------------------- /composer-dependency-analyser.php: -------------------------------------------------------------------------------- 1 | ignoreErrorsOnPackage('terminal42/contao-changelanguage', [ErrorType::DEV_DEPENDENCY_IN_PROD]) 9 | ->ignoreErrorsOnPackage('terminal42/dc_multilingual', [ErrorType::DEV_DEPENDENCY_IN_PROD]) 10 | ; 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codefog/contao-news_categories", 3 | "description": "News Categories bundle for Contao Open Source CMS", 4 | "keywords": ["contao", "news", "categories"], 5 | "type": "contao-bundle", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Codefog", 10 | "homepage": "https://codefog.pl" 11 | } 12 | ], 13 | "require": { 14 | "php": "^8.1", 15 | "contao/core-bundle": "^5.1.9", 16 | "contao/news-bundle": "^5.1.9", 17 | "codefog/contao-haste": "^5.0", 18 | "doctrine/dbal": "^3.0", 19 | "symfony/config": "^6.0 || ^7.0", 20 | "symfony/dependency-injection": "^6.0 || ^7.0", 21 | "symfony/event-dispatcher": "^6.0 || ^7.0", 22 | "symfony/http-foundation": "^6.0 || ^7.0", 23 | "symfony/http-kernel": "^6.0 || ^7.0", 24 | "symfony/security-bundle": "^6.0 || ^7.0", 25 | "symfony/security-core": "^6.0 || ^7.0", 26 | "symfony/service-contracts": "^3.0", 27 | "symfony/translation-contracts": "^3.0" 28 | }, 29 | "require-dev": { 30 | "contao/manager-plugin": "^2.0", 31 | "terminal42/contao-changelanguage": "^3.1", 32 | "terminal42/dc_multilingual": "^4.5", 33 | "terminal42/contao-build-tools": "dev-main" 34 | }, 35 | "suggest": { 36 | "terminal42/contao-changelanguage": "^3.1", 37 | "terminal42/dc_multilingual": "^4.0" 38 | }, 39 | "conflict": { 40 | "contao/manager-plugin": "<2.0 || >=3.0", 41 | "terminal42/dc_multilingual": "<4.0 || >=5.0", 42 | "terminal42/contao-changelanguage": "<3.1 || >=4.0" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "Codefog\\NewsCategoriesBundle\\": "src" 47 | } 48 | }, 49 | "extra": { 50 | "contao-manager-plugin": "Codefog\\NewsCategoriesBundle\\ContaoManager\\Plugin" 51 | }, 52 | "config": { 53 | "allow-plugins": { 54 | "contao-components/installer": true, 55 | "contao/manager-plugin": true, 56 | "terminal42/contao-build-tools": true, 57 | "php-http/discovery": false 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autoconfigure: true 4 | autowire: true 5 | public: false 6 | bind: 7 | $projectDir: '%kernel.project_dir%' 8 | 9 | Codefog\NewsCategoriesBundle\: 10 | resource: '../src' 11 | exclude: '../src/{ContaoManager,DependencyInjection,ContentElement,Exception,FrontendModule,Model,CodefogNewsCategoriesBundle.php}' 12 | -------------------------------------------------------------------------------- /contao/config/config.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Codefog\NewsCategoriesBundle\ContentElement\NewsFilterElement; 12 | use Codefog\NewsCategoriesBundle\FrontendModule\CumulativeFilterModule; 13 | use Codefog\NewsCategoriesBundle\FrontendModule\CumulativeHierarchicalFilterModule; 14 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsArchiveModule; 15 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsCategoriesModule; 16 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsListModule; 17 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsMenuModule; 18 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 19 | 20 | $GLOBALS['BE_MOD']['content']['news']['tables'][] = 'tl_news_category'; 21 | 22 | /* 23 | * Front end modules 24 | */ 25 | $GLOBALS['FE_MOD']['news']['newsarchive'] = NewsArchiveModule::class; 26 | $GLOBALS['FE_MOD']['news']['newscategories'] = NewsCategoriesModule::class; 27 | $GLOBALS['FE_MOD']['news']['newscategories_cumulative'] = CumulativeFilterModule::class; 28 | $GLOBALS['FE_MOD']['news']['newscategories_cumulativehierarchical'] = CumulativeHierarchicalFilterModule::class; 29 | $GLOBALS['FE_MOD']['news']['newslist'] = NewsListModule::class; 30 | $GLOBALS['FE_MOD']['news']['newsmenu'] = NewsMenuModule::class; 31 | 32 | /* 33 | * Content elements 34 | */ 35 | $GLOBALS['TL_CTE']['includes']['newsfilter'] = NewsFilterElement::class; 36 | 37 | /* 38 | * Models 39 | */ 40 | $GLOBALS['TL_MODELS']['tl_news_category'] = NewsCategoryModel::class; 41 | 42 | /* 43 | * Add permissions 44 | */ 45 | $GLOBALS['TL_PERMISSIONS'][] = 'newscategories'; 46 | $GLOBALS['TL_PERMISSIONS'][] = 'newscategories_default'; 47 | $GLOBALS['TL_PERMISSIONS'][] = 'newscategories_roots'; 48 | -------------------------------------------------------------------------------- /contao/dca/tl_content.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\Controller; 12 | use Contao\System; 13 | 14 | Controller::loadDataContainer('tl_module'); 15 | System::loadLanguageFile('tl_module'); 16 | 17 | /* 18 | * Add palettes 19 | */ 20 | $GLOBALS['TL_DCA']['tl_content']['palettes']['newsfilter'] = '{type_legend},type,headline;{include_legend},news_module,news_filterCategories,news_relatedCategories,news_filterDefault,news_filterPreserve;{link_legend:hide},news_categoryFilterPage;{image_legend:hide},news_categoryImgSize;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space;{invisible_legend:hide},invisible,start,stop'; 21 | 22 | /* 23 | * Add fields 24 | */ 25 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_filterCategories'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_filterCategories']; 26 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_relatedCategories'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_relatedCategories']; 27 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_includeSubcategories'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_includeSubcategories']; 28 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_filterDefault'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_filterDefault']; 29 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_filterPreserve'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_filterPreserve']; 30 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_categoryFilterPage'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryFilterPage']; 31 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_categoryImgSize'] = &$GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryImgSize']; 32 | 33 | $GLOBALS['TL_DCA']['tl_content']['fields']['news_module'] = [ 34 | 'label' => &$GLOBALS['TL_LANG']['tl_content']['module'], 35 | 'exclude' => true, 36 | 'inputType' => 'select', 37 | // 'options_callback' => ContentNewsModuleOptionsListener 38 | 'eval' => ['mandatory' => true, 'chosen' => true, 'submitOnChange' => true], 39 | 'wizard' => [['tl_content', 'editModule']], 40 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 41 | ]; 42 | -------------------------------------------------------------------------------- /contao/dca/tl_module.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 12 | 13 | $GLOBALS['TL_DCA']['tl_module']['palettes']['__selector__'][] = 'news_customCategories'; 14 | $GLOBALS['TL_DCA']['tl_module']['palettes']['__selector__'][] = 'news_relatedCategories'; 15 | $GLOBALS['TL_DCA']['tl_module']['palettes']['newscategories'] = '{title_legend},name,headline,type;{config_legend},news_archives,news_showQuantity,news_resetCategories,news_showEmptyCategories,news_enableCanonicalUrls,news_includeSubcategories,showLevel;{reference_legend:hide},news_categoriesRoot,news_customCategories;{redirect_legend:hide},news_forceCategoryUrl,jumpTo;{template_legend:hide},navigationTpl,customTpl;{image_legend:hide},news_categoryImgSize;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space'; 16 | $GLOBALS['TL_DCA']['tl_module']['palettes']['newscategories_cumulative'] = '{title_legend},name,headline,type;{config_legend},news_archives,news_showQuantity,news_resetCategories,news_enableCanonicalUrls,news_includeSubcategories,news_filterCategoriesUnion;{reference_legend:hide},news_categoriesRoot,news_customCategories;{redirect_legend:hide},jumpTo;{template_legend:hide},navigationTpl,customTpl;{image_legend:hide},news_categoryImgSize;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space'; 17 | $GLOBALS['TL_DCA']['tl_module']['palettes']['newscategories_cumulativehierarchical'] = '{title_legend},name,headline,type;{config_legend},news_archives,news_showQuantity,news_resetCategories,news_showEmptyCategories,news_enableCanonicalUrls,news_filterCategoriesUnion,news_includeSubcategories,showLevel;{reference_legend:hide},news_categoriesRoot,news_customCategories;{redirect_legend:hide},news_forceCategoryUrl,jumpTo;{template_legend:hide},navigationTpl,customTpl;{image_legend:hide},news_categoryImgSize;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space'; 18 | $GLOBALS['TL_DCA']['tl_module']['subpalettes']['news_customCategories'] = 'news_categories'; 19 | $GLOBALS['TL_DCA']['tl_module']['subpalettes']['news_relatedCategories'] = 'news_relatedCategoriesOrder,news_categoriesRoot'; 20 | 21 | PaletteManipulator::create() 22 | ->addLegend('redirect_legend', 'config_legend', PaletteManipulator::POSITION_AFTER) 23 | ->addField('news_filterCategories', 'config_legend', PaletteManipulator::POSITION_APPEND) 24 | ->addField('news_filterCategoriesCumulative', 'config_legend', PaletteManipulator::POSITION_APPEND) 25 | ->addField('news_filterCategoriesUnion', 'config_legend', PaletteManipulator::POSITION_APPEND) 26 | ->addField('news_relatedCategories', 'config_legend', PaletteManipulator::POSITION_APPEND) 27 | ->addField('news_includeSubcategories', 'config_legend', PaletteManipulator::POSITION_APPEND) 28 | ->addField('news_filterDefault', 'config_legend', PaletteManipulator::POSITION_APPEND) 29 | ->addField('news_filterPreserve', 'config_legend', PaletteManipulator::POSITION_APPEND) 30 | ->addField('news_categoryFilterPage', 'redirect_legend', PaletteManipulator::POSITION_APPEND) 31 | ->addField('news_categoryImgSize', 'image_legend', PaletteManipulator::POSITION_APPEND) 32 | ->applyToPalette('newslist', 'tl_module') 33 | ; 34 | 35 | PaletteManipulator::create() 36 | ->addField('news_filterCategories', 'config_legend', PaletteManipulator::POSITION_APPEND) 37 | ->addField('news_filterCategoriesCumulative', 'config_legend', PaletteManipulator::POSITION_APPEND) 38 | ->addField('news_filterCategoriesUnion', 'config_legend', PaletteManipulator::POSITION_APPEND) 39 | ->addField('news_includeSubcategories', 'config_legend', PaletteManipulator::POSITION_APPEND) 40 | ->addField('news_filterDefault', 'config_legend', PaletteManipulator::POSITION_APPEND) 41 | ->addField('news_filterPreserve', 'config_legend', PaletteManipulator::POSITION_APPEND) 42 | ->addField('news_categoryImgSize', 'image_legend', PaletteManipulator::POSITION_APPEND) 43 | ->applyToPalette('newsarchive', 'tl_module') 44 | ->applyToPalette('newsmenu', 'tl_module') 45 | ; 46 | 47 | PaletteManipulator::create() 48 | ->addLegend('redirect_legend', 'config_legend', PaletteManipulator::POSITION_AFTER) 49 | ->addField('news_categoryFilterPage', 'redirect_legend', PaletteManipulator::POSITION_APPEND) 50 | ->applyToPalette('newsarchive', 'tl_module') 51 | ; 52 | 53 | PaletteManipulator::create() 54 | ->addLegend('redirect_legend', 'config_legend', PaletteManipulator::POSITION_AFTER) 55 | ->addField('news_categoryFilterPage', 'redirect_legend', PaletteManipulator::POSITION_APPEND) 56 | ->addField('news_categoryImgSize', 'image_legend', PaletteManipulator::POSITION_APPEND) 57 | ->applyToPalette('newsreader', 'tl_module') 58 | ; 59 | 60 | /* 61 | * Add fields 62 | */ 63 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_categories'] = [ 64 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_categories'], 65 | 'exclude' => true, 66 | 'inputType' => 'picker', 67 | 'foreignKey' => 'tl_news_category.title', 68 | 'eval' => ['multiple' => true, 'fieldType' => 'checkbox'], 69 | 'sql' => ['type' => 'blob', 'notnull' => false], 70 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 71 | ]; 72 | 73 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_customCategories'] = [ 74 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_customCategories'], 75 | 'exclude' => true, 76 | 'inputType' => 'checkbox', 77 | 'eval' => ['submitOnChange' => true, 'tl_class' => 'clr'], 78 | 'sql' => ['type' => 'boolean', 'default' => 0], 79 | ]; 80 | 81 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_filterCategories'] = [ 82 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_filterCategories'], 83 | 'exclude' => true, 84 | 'inputType' => 'checkbox', 85 | 'eval' => ['tl_class' => 'w50'], 86 | 'sql' => ['type' => 'boolean', 'default' => 0], 87 | ]; 88 | 89 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_filterCategoriesCumulative'] = [ 90 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_filterCategoriesCumulative'], 91 | 'exclude' => true, 92 | 'inputType' => 'checkbox', 93 | 'eval' => ['tl_class' => 'clr'], 94 | 'sql' => ['type' => 'boolean', 'default' => 0], 95 | ]; 96 | 97 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_relatedCategories'] = [ 98 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_relatedCategories'], 99 | 'exclude' => true, 100 | 'inputType' => 'checkbox', 101 | 'eval' => ['submitOnChange' => true, 'tl_class' => 'clr'], 102 | 'sql' => ['type' => 'boolean', 'default' => 0], 103 | ]; 104 | 105 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_relatedCategoriesOrder'] = [ 106 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_relatedCategoriesOrder'], 107 | 'exclude' => true, 108 | 'inputType' => 'select', 109 | 'options' => ['default', 'best_match'], 110 | 'reference' => &$GLOBALS['TL_LANG']['tl_module']['news_relatedCategoriesOrderRef'], 111 | 'eval' => ['tl_class' => 'w50'], 112 | 'sql' => ['type' => 'string', 'length' => 10, 'default' => ''], 113 | ]; 114 | 115 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_includeSubcategories'] = [ 116 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_includeSubcategories'], 117 | 'exclude' => true, 118 | 'inputType' => 'checkbox', 119 | 'eval' => ['tl_class' => 'clr'], 120 | 'sql' => ['type' => 'boolean', 'default' => 0], 121 | ]; 122 | 123 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_filterCategoriesUnion'] = [ 124 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_filterCategoriesUnion'], 125 | 'exclude' => true, 126 | 'inputType' => 'checkbox', 127 | 'eval' => ['tl_class' => 'clr'], 128 | 'sql' => ['type' => 'boolean', 'default' => 0], 129 | ]; 130 | 131 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_enableCanonicalUrls'] = [ 132 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_enableCanonicalUrls'], 133 | 'exclude' => true, 134 | 'inputType' => 'checkbox', 135 | 'eval' => ['tl_class' => 'w50'], 136 | 'sql' => ['type' => 'boolean', 'default' => 0], 137 | ]; 138 | 139 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_filterDefault'] = [ 140 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_filterDefault'], 141 | 'exclude' => true, 142 | 'inputType' => 'picker', 143 | 'foreignKey' => 'tl_news_category.title', 144 | 'eval' => ['multiple' => true, 'fieldType' => 'checkbox', 'tl_class' => 'clr'], 145 | 'sql' => ['type' => 'blob', 'notnull' => false], 146 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 147 | ]; 148 | 149 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_filterPreserve'] = [ 150 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_filterPreserve'], 151 | 'exclude' => true, 152 | 'inputType' => 'checkbox', 153 | 'eval' => ['tl_class' => 'clr'], 154 | 'sql' => ['type' => 'boolean', 'default' => 0], 155 | ]; 156 | 157 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_resetCategories'] = [ 158 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_resetCategories'], 159 | 'exclude' => true, 160 | 'inputType' => 'checkbox', 161 | 'eval' => ['tl_class' => 'w50'], 162 | 'sql' => ['type' => 'boolean', 'default' => 0], 163 | ]; 164 | 165 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_showEmptyCategories'] = [ 166 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_showEmptyCategories'], 167 | 'exclude' => true, 168 | 'inputType' => 'checkbox', 169 | 'eval' => ['tl_class' => 'w50'], 170 | 'sql' => ['type' => 'boolean', 'default' => 0], 171 | ]; 172 | 173 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_forceCategoryUrl'] = [ 174 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_forceCategoryUrl'], 175 | 'exclude' => true, 176 | 'inputType' => 'checkbox', 177 | 'eval' => ['tl_class' => 'clr'], 178 | 'sql' => ['type' => 'boolean', 'default' => 0], 179 | ]; 180 | 181 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_categoriesRoot'] = [ 182 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_categoriesRoot'], 183 | 'exclude' => true, 184 | 'inputType' => 'picker', 185 | 'foreignKey' => 'tl_news_category.title', 186 | 'eval' => ['fieldType' => 'radio', 'tl_class' => 'clr'], 187 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 188 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 189 | ]; 190 | 191 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryFilterPage'] = [ 192 | 'label' => &$GLOBALS['TL_LANG']['tl_module']['news_categoryFilterPage'], 193 | 'exclude' => true, 194 | 'inputType' => 'pageTree', 195 | 'eval' => ['fieldType' => 'radio', 'tl_class' => 'clr'], 196 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 197 | ]; 198 | 199 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryImgSize'] = $GLOBALS['TL_DCA']['tl_module']['fields']['imgSize']; 200 | unset($GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryImgSize']['label']); 201 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryImgSize']['label'] = &$GLOBALS['TL_LANG']['tl_module']['news_categoryImgSize']; 202 | $GLOBALS['TL_DCA']['tl_module']['fields']['news_categoryImgSize']['options_callback'] = ['contao.listener.image_size_options', '__invoke']; 203 | -------------------------------------------------------------------------------- /contao/dca/tl_news.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 12 | 13 | /* 14 | * Extend palettes 15 | */ 16 | $paletteManipulator = PaletteManipulator::create() 17 | ->addLegend('category_legend', 'title_legend', PaletteManipulator::POSITION_AFTER) 18 | ->addField('categories', 'category_legend', PaletteManipulator::POSITION_APPEND) 19 | ; 20 | 21 | foreach ($GLOBALS['TL_DCA']['tl_news']['palettes'] as $name => $palette) { 22 | if (is_string($palette)) { 23 | $paletteManipulator->applyToPalette($name, 'tl_news'); 24 | } 25 | } 26 | 27 | /* 28 | * Add fields 29 | */ 30 | $GLOBALS['TL_DCA']['tl_news']['fields']['categories'] = [ 31 | 'label' => &$GLOBALS['TL_LANG']['tl_news']['categories'], 32 | 'exclude' => true, 33 | 'filter' => true, 34 | 'inputType' => 'picker', 35 | 'foreignKey' => 'tl_news_category.title', 36 | // 'options_callback' => NewsCategoriesOptionsListener 37 | 'eval' => ['multiple' => true, 'fieldType' => 'checkbox'], 38 | 'relation' => [ 39 | 'type' => 'haste-ManyToMany', 40 | 'load' => 'lazy', 41 | 'table' => 'tl_news_category', 42 | 'referenceColumn' => 'news_id', 43 | 'fieldColumn' => 'category_id', 44 | 'relationTable' => 'tl_news_categories', 45 | ], 46 | ]; 47 | -------------------------------------------------------------------------------- /contao/dca/tl_news_archive.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 12 | 13 | /* 14 | * Extend palettes 15 | */ 16 | $GLOBALS['TL_DCA']['tl_news_archive']['palettes']['__selector__'][] = 'limitCategories'; 17 | $GLOBALS['TL_DCA']['tl_news_archive']['subpalettes']['limitCategories'] = 'categories'; 18 | 19 | PaletteManipulator::create() 20 | ->addLegend('categories_legend', 'title_legend', PaletteManipulator::POSITION_AFTER) 21 | ->addField('limitCategories', 'categories_legend', PaletteManipulator::POSITION_APPEND) 22 | ->applyToPalette('default', 'tl_news_archive') 23 | ; 24 | 25 | /* 26 | * Add fields to tl_news_archive 27 | */ 28 | $GLOBALS['TL_DCA']['tl_news_archive']['fields']['limitCategories'] = [ 29 | 'label' => &$GLOBALS['TL_LANG']['tl_news_archive']['limitCategories'], 30 | 'exclude' => true, 31 | 'inputType' => 'checkbox', 32 | 'eval' => ['submitOnChange' => true], 33 | 'sql' => ['type' => 'boolean', 'default' => 0], 34 | ]; 35 | 36 | $GLOBALS['TL_DCA']['tl_news_archive']['fields']['categories'] = [ 37 | 'label' => &$GLOBALS['TL_LANG']['tl_news_archive']['categories'], 38 | 'exclude' => true, 39 | 'filter' => true, 40 | 'inputType' => 'picker', 41 | 'foreignKey' => 'tl_news_category.title', 42 | // 'options_callback' => NewsCategoriesOptionsListener 43 | 'eval' => ['mandatory' => true, 'multiple' => true, 'fieldType' => 'checkbox'], 44 | 'sql' => ['type' => 'blob', 'notnull' => false], 45 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 46 | ]; 47 | -------------------------------------------------------------------------------- /contao/dca/tl_news_category.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\Config; 12 | use Contao\DataContainer; 13 | use Contao\DC_Table; 14 | use Contao\System; 15 | use Terminal42\DcMultilingualBundle\Driver; 16 | 17 | System::loadLanguageFile('tl_news_archive'); 18 | 19 | /* 20 | * Table tl_news_category 21 | */ 22 | $GLOBALS['TL_DCA']['tl_news_category'] = [ 23 | // Config 24 | 'config' => [ 25 | 'label' => $GLOBALS['TL_LANG']['tl_news_archive']['categories'][0], 26 | 'dataContainer' => DC_Table::class, 27 | 'enableVersioning' => true, 28 | 'backlink' => 'do=news', 29 | 'sql' => [ 30 | 'keys' => [ 31 | 'id' => 'primary', 32 | 'pid' => 'index', 33 | 'alias' => 'index', 34 | ], 35 | ], 36 | ], 37 | 38 | // List 39 | 'list' => [ 40 | 'sorting' => [ 41 | 'mode' => DataContainer::MODE_TREE, 42 | 'rootPaste' => true, 43 | 'icon' => 'bundles/codefognewscategories/icon.png', 44 | 'panelLayout' => 'filter;search', 45 | ], 46 | 'label' => [ 47 | 'fields' => ['title', 'frontendTitle'], 48 | 'format' => '%s [%s]', 49 | ], 50 | 'global_operations' => [ 51 | 'toggleNodes' => [ 52 | 'label' => &$GLOBALS['TL_LANG']['MSC']['toggleAll'], 53 | 'href' => 'ptg=all', 54 | 'class' => 'header_toggle', 55 | ], 56 | 'all' => [ 57 | 'label' => &$GLOBALS['TL_LANG']['MSC']['all'], 58 | 'href' => 'act=select', 59 | 'class' => 'header_edit_all', 60 | 'attributes' => 'onclick="Backend.getScrollOffset()" accesskey="e"', 61 | ], 62 | ], 63 | ], 64 | 65 | // Palettes 66 | 'palettes' => [ 67 | 'default' => '{title_legend},title,alias,frontendTitle,cssClass;{details_legend:hide},description,image;{modules_legend:hide},hideInList,hideInReader,excludeInRelated;{redirect_legend:hide},jumpTo;{publish_legend},published', 68 | ], 69 | 70 | // Fields 71 | 'fields' => [ 72 | 'id' => [ 73 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'autoincrement' => true], 74 | ], 75 | 'pid' => [ 76 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 77 | ], 78 | 'sorting' => [ 79 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 80 | ], 81 | 'tstamp' => [ 82 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 83 | ], 84 | 'title' => [ 85 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['title'], 86 | 'search' => true, 87 | 'inputType' => 'text', 88 | 'eval' => ['mandatory' => true, 'tl_class' => 'w50'], 89 | 'sql' => ['type' => 'string', 'default' => ''], 90 | ], 91 | 'frontendTitle' => [ 92 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['frontendTitle'], 93 | 'search' => true, 94 | 'inputType' => 'text', 95 | 'eval' => ['tl_class' => 'w50'], 96 | 'sql' => ['type' => 'string', 'length' => 64, 'default' => ''], 97 | ], 98 | 'alias' => [ 99 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['alias'], 100 | 'search' => true, 101 | 'inputType' => 'text', 102 | 'eval' => [ 103 | 'rgxp' => 'alias', 104 | 'doNotCopy' => true, 105 | 'alwaysSave' => true, 106 | 'maxlength' => 128, 107 | 'tl_class' => 'w50', 108 | ], 109 | 'sql' => ['type' => 'binary', 'length' => 128, 'default' => ''], 110 | ], 111 | 'cssClass' => [ 112 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['cssClass'], 113 | 'search' => true, 114 | 'inputType' => 'text', 115 | 'eval' => ['tl_class' => 'w50'], 116 | 'sql' => ['type' => 'string', 'default' => ''], 117 | ], 118 | 'description' => [ 119 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['description'], 120 | 'search' => true, 121 | 'inputType' => 'textarea', 122 | 'eval' => ['rte' => 'tinyMCE', 'helpwizard' => true, 'tl_class' => 'clr'], 123 | 'explanation' => 'insertTags', 124 | 'sql' => ['type' => 'text', 'notnull' => false], 125 | ], 126 | 'image' => [ 127 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['image'], 128 | 'inputType' => 'fileTree', 129 | 'eval' => [ 130 | 'files' => true, 131 | 'filesOnly' => true, 132 | 'fieldType' => 'radio', 133 | 'extensions' => Config::get('validImageTypes'), 134 | 'tl_class' => 'clr', 135 | ], 136 | 'sql' => ['type' => 'binary', 'length' => 16, 'notnull' => false], 137 | ], 138 | 'hideInList' => [ 139 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['hideInList'], 140 | 'filter' => true, 141 | 'inputType' => 'checkbox', 142 | 'eval' => ['tl_class' => 'w50'], 143 | 'sql' => ['type' => 'boolean', 'default' => 0], 144 | ], 145 | 'hideInReader' => [ 146 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['hideInReader'], 147 | 'filter' => true, 148 | 'inputType' => 'checkbox', 149 | 'eval' => ['tl_class' => 'w50'], 150 | 'sql' => ['type' => 'boolean', 'default' => 0], 151 | ], 152 | 'excludeInRelated' => [ 153 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['excludeInRelated'], 154 | 'filter' => true, 155 | 'inputType' => 'checkbox', 156 | 'eval' => ['tl_class' => 'w50'], 157 | 'sql' => ['type' => 'boolean', 'default' => 0], 158 | ], 159 | 'jumpTo' => [ 160 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['jumpTo'], 161 | 'inputType' => 'pageTree', 162 | 'eval' => ['fieldType' => 'radio'], 163 | 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], 164 | ], 165 | 'published' => [ 166 | 'label' => &$GLOBALS['TL_LANG']['tl_news_category']['published'], 167 | 'filter' => true, 168 | 'toggle' => true, 169 | 'inputType' => 'checkbox', 170 | 'sql' => ['type' => 'boolean', 'default' => 0], 171 | ], 172 | ], 173 | ]; 174 | 175 | /* 176 | * Enable multilingual features 177 | */ 178 | if (class_exists(Driver::class)) { 179 | // Config 180 | $GLOBALS['TL_DCA']['tl_news_category']['config']['dataContainer'] = Driver::class; 181 | $GLOBALS['TL_DCA']['tl_news_category']['config']['langColumn'] = 'language'; 182 | $GLOBALS['TL_DCA']['tl_news_category']['config']['langPid'] = 'lid'; 183 | $GLOBALS['TL_DCA']['tl_news_category']['config']['sql']['keys']['language,lid'] = 'index'; 184 | 185 | // Fields 186 | $GLOBALS['TL_DCA']['tl_news_category']['fields']['language']['sql'] = ['type' => 'string', 'length' => 5, 'default' => '']; 187 | $GLOBALS['TL_DCA']['tl_news_category']['fields']['lid']['sql'] = ['type' => 'integer', 'unsigned' => true, 'default' => 0]; 188 | $GLOBALS['TL_DCA']['tl_news_category']['fields']['title']['eval']['translatableFor'] = '*'; 189 | $GLOBALS['TL_DCA']['tl_news_category']['fields']['frontendTitle']['eval']['translatableFor'] = '*'; 190 | $GLOBALS['TL_DCA']['tl_news_category']['fields']['description']['eval']['translatableFor'] = '*'; 191 | 192 | $GLOBALS['TL_DCA']['tl_news_category']['fields']['alias']['eval']['translatableFor'] = '*'; 193 | unset($GLOBALS['TL_DCA']['tl_news_category']['fields']['alias']['eval']['spaceToUnderscore'], $GLOBALS['TL_DCA']['tl_news_category']['fields']['alias']['eval']['unique']); 194 | } 195 | -------------------------------------------------------------------------------- /contao/dca/tl_page.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 12 | 13 | PaletteManipulator::create() 14 | ->addLegend('newsCategories_legend', 'global_legend', PaletteManipulator::POSITION_AFTER, true) 15 | ->addField('newsCategories_param', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 16 | ->applyToPalette('root', 'tl_page') 17 | ->applyToPalette('rootfallback', 'tl_page') 18 | ; 19 | 20 | PaletteManipulator::create() 21 | ->addField(['newsCategories', 'newsCategories_show'], 'newsArchives') 22 | ->applyToPalette('news_feed', 'tl_page') 23 | ; 24 | 25 | /* 26 | * Add fields 27 | */ 28 | $GLOBALS['TL_DCA']['tl_page']['fields']['newsCategories_param'] = [ 29 | 'label' => &$GLOBALS['TL_LANG']['tl_page']['newsCategories_param'], 30 | 'exclude' => true, 31 | 'inputType' => 'text', 32 | 'eval' => ['maxlength' => 64, 'rgxp' => 'alias', 'tl_class' => 'w50'], 33 | 'sql' => ['type' => 'string', 'length' => 64, 'default' => ''], 34 | ]; 35 | 36 | $GLOBALS['TL_DCA']['tl_page']['fields']['newsCategories'] = [ 37 | 'exclude' => true, 38 | 'filter' => true, 39 | 'inputType' => 'picker', 40 | 'foreignKey' => 'tl_news_category.title', 41 | 'eval' => ['multiple' => true, 'fieldType' => 'checkbox'], 42 | 'sql' => ['type' => 'blob', 'notnull' => false], 43 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 44 | ]; 45 | 46 | $GLOBALS['TL_DCA']['tl_page']['fields']['newsCategories_show'] = [ 47 | 'exclude' => true, 48 | 'filter' => true, 49 | 'inputType' => 'select', 50 | 'options' => ['title', 'text_before', 'text_after'], 51 | 'reference' => &$GLOBALS['TL_LANG']['tl_page']['newsCategories_show'], 52 | 'eval' => [ 53 | 'includeBlankOption' => true, 54 | 'blankOptionLabel' => $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['empty'] ?? null, 55 | 'tl_class' => 'w50', 56 | ], 57 | 'sql' => ['type' => 'string', 'length' => 16, 'default' => ''], 58 | ]; 59 | -------------------------------------------------------------------------------- /contao/dca/tl_settings.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 12 | 13 | $GLOBALS['TL_DCA']['tl_settings']['fields']['news_categorySlugSetting'] = [ 14 | 'inputType' => 'select', 15 | 'eval' => ['tl_class' => 'w50', 'includeBlankOption' => true, 'decodeEntities' => true], 16 | ]; 17 | 18 | PaletteManipulator::create() 19 | ->addLegend('news_categories_legend', null, PaletteManipulator::POSITION_AFTER, true) 20 | ->addField('news_categorySlugSetting', 'news_categories_legend', PaletteManipulator::POSITION_APPEND) 21 | ->applyToPalette('default', 'tl_settings') 22 | ; 23 | -------------------------------------------------------------------------------- /contao/dca/tl_user.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 12 | 13 | PaletteManipulator::create() 14 | ->addLegend('newsCategories_legend', 'news_legend', PaletteManipulator::POSITION_AFTER) 15 | ->addField('newscategories', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 16 | ->addField('newscategories_roots', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 17 | ->addField('newscategories_default', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 18 | ->applyToPalette('extend', 'tl_user') 19 | ->applyToPalette('custom', 'tl_user') 20 | ; 21 | 22 | /* 23 | * Add fields 24 | */ 25 | $GLOBALS['TL_DCA']['tl_user']['fields']['newscategories'] = [ 26 | 'label' => &$GLOBALS['TL_LANG']['tl_user']['newscategories'], 27 | 'exclude' => true, 28 | 'inputType' => 'checkbox', 29 | 'options' => ['manage'], 30 | 'reference' => &$GLOBALS['TL_LANG']['tl_user']['newscategoriesRef'], 31 | 'eval' => ['multiple' => true, 'tl_class' => 'clr'], 32 | 'sql' => ['type' => 'string', 'length' => 32, 'default' => ''], 33 | ]; 34 | 35 | $GLOBALS['TL_DCA']['tl_user']['fields']['newscategories_roots'] = [ 36 | 'label' => &$GLOBALS['TL_LANG']['tl_user']['newscategories_roots'], 37 | 'exclude' => true, 38 | 'inputType' => 'picker', 39 | 'foreignKey' => 'tl_news_category.title', 40 | 'eval' => ['multiple' => true, 'fieldType' => 'checkbox'], 41 | 'sql' => ['type' => 'blob', 'notnull' => false], 42 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 43 | ]; 44 | 45 | $GLOBALS['TL_DCA']['tl_user']['fields']['newscategories_default'] = [ 46 | 'label' => &$GLOBALS['TL_LANG']['tl_user']['newscategories_default'], 47 | 'exclude' => true, 48 | 'inputType' => 'picker', 49 | 'foreignKey' => 'tl_news_category.title', 50 | 'eval' => ['multiple' => true, 'fieldType' => 'checkbox'], 51 | 'sql' => ['type' => 'blob', 'notnull' => false], 52 | 'relation' => ['type' => 'hasMany', 'load' => 'lazy'], 53 | ]; 54 | -------------------------------------------------------------------------------- /contao/dca/tl_user_group.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\Controller; 12 | use Contao\CoreBundle\DataContainer\PaletteManipulator; 13 | use Contao\System; 14 | 15 | Controller::loadDataContainer('tl_user'); 16 | System::loadLanguageFile('tl_user'); 17 | 18 | /* 19 | * Extend palettes 20 | */ 21 | PaletteManipulator::create() 22 | ->addLegend('newsCategories_legend', 'news_legend', PaletteManipulator::POSITION_AFTER) 23 | ->addField('newscategories', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 24 | ->addField('newscategories_roots', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 25 | ->addField('newscategories_default', 'newsCategories_legend', PaletteManipulator::POSITION_APPEND) 26 | ->applyToPalette('default', 'tl_user_group') 27 | ; 28 | 29 | /* 30 | * Add fields 31 | */ 32 | $GLOBALS['TL_DCA']['tl_user_group']['fields']['newscategories'] = &$GLOBALS['TL_DCA']['tl_user']['fields']['newscategories']; 33 | $GLOBALS['TL_DCA']['tl_user_group']['fields']['newscategories_default'] = &$GLOBALS['TL_DCA']['tl_user']['fields']['newscategories_default']; 34 | $GLOBALS['TL_DCA']['tl_user_group']['fields']['newscategories_roots'] = &$GLOBALS['TL_DCA']['tl_user']['fields']['newscategories_roots']; 35 | -------------------------------------------------------------------------------- /contao/languages/de/default.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Miscellaneous. 13 | */ 14 | $GLOBALS['TL_LANG']['MSC']['newsCategories'] = 'Kategorien:'; 15 | $GLOBALS['TL_LANG']['MSC']['resetCategories'] = ['Alle Kategorien', 'Zeigt Nachrichten aus allen Kategorien']; 16 | $GLOBALS['TL_LANG']['MSC']['resetCategoriesCumulative'] = ['Kategorien zurücksetzen', 'Nachrichten aller Kategorien zeigen']; 17 | $GLOBALS['TL_LANG']['MSC']['activeCategories'] = 'Aktive Kategorien:'; 18 | $GLOBALS['TL_LANG']['MSC']['inactiveCategories'] = 'Nach Kategorien filtern:'; 19 | $GLOBALS['TL_LANG']['MSC']['inactiveCategoriesAdd'] = 'Dem Filter weitere Kategorien hinzufügen:'; 20 | 21 | /* 22 | * Content elements 23 | */ 24 | $GLOBALS['TL_LANG']['CTE']['newsfilter'] = ['Nachrichten-Modul-Filter', 'Nachrichtenlisten bzw. Nachrichtenarchive mit Kategorie-Filter.']; 25 | -------------------------------------------------------------------------------- /contao/languages/de/modules.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Front end modules. 13 | */ 14 | $GLOBALS['TL_LANG']['FMD']['newscategories'] = ['Liste mit Nachrichten-Kategorien', 'Fügt eine Nachrichten-Kategorieliste in die Seite ein.']; 15 | $GLOBALS['TL_LANG']['FMD']['newscategories_cumulative'] = ['Kumulativer Kategorie Filter', 'Fügt einen kumulativen Filter nach Kategorien hinzu.']; 16 | $GLOBALS['TL_LANG']['FMD']['newscategories_cumulativehierarchical'] = ['Kumulativer Hierarchischer Kategorie Filter', 'Fügt einen kumulativen hierarchischen Filter nach Kategorien hinzu.']; 17 | -------------------------------------------------------------------------------- /contao/languages/de/tl_module.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_module']['news_categories'] = ['Nachrichten-Kategorien', 'Bitte wähle die Nachrichten-Kategorien.']; 15 | $GLOBALS['TL_LANG']['tl_module']['news_customCategories'] = ['Auswählbare Kategorien', 'Kategorien, deren Auswahl möglich sein soll.']; 16 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategories'] = ['Nach Kategorien filtern', 'Filtert die Nachrichten-Liste nach Kategorien.']; 17 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategoriesCumulative'] = ['Nach Kategorien filtern (kumulativ)', 'Filtert die Nachrichtenliste kumulativ nach Kategorien. Priorität gegenüber der vorhergehenden Checkbox ("Nach Kategorien filtern").']; 18 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategories'] = ['Nach verwandten Kategorien filtern', 'Nutzt die Kategorien des aktuellen Nachrichtenbeitrags um die Nachrichtenliste zu filtern. Hinweis: dieses Modul muss sich auf der selben Seite befinden wie das Nachrichtenleser-Modul.']; 19 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategoriesOrder'] = ['Sortierung der Beiträge verwandter Kategorien', 'Hier kann die Sortierung der verwandten Nachrichtenbeiträge ausgewählt werden.']; 20 | $GLOBALS['TL_LANG']['tl_module']['news_includeSubcategories'] = ['Unterkategorien miteinbeziehen', 'Berücksichtigt Unterkategorien bei der Filterung wenn eine übergeordnete Kategorie aktiv ist.']; 21 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategoriesUnion'] = ['Kategorie Filterung über Vereinigung (nur kumulativ)', 'Filtert Nachrichtenbeiträge und Kategorien vereinigt (x OR y) statt der Schnittmenge (x AND y).']; 22 | $GLOBALS['TL_LANG']['tl_module']['news_enableCanonicalUrls'] = ['Canonical URL einfügen', 'Fügt bei aktiver Kategorieauswahl einen Canonical-Tag im Head der Webseite ein.']; 23 | $GLOBALS['TL_LANG']['tl_module']['news_filterDefault'] = ['Standard-Filter', 'Hier kann der Standard-Filter für die Nachrichtenliste gewählt werden.']; 24 | $GLOBALS['TL_LANG']['tl_module']['news_filterPreserve'] = ['Standard-Filter aktivieren', 'Die Standard-Filtereinstellungen gelten auch dann, wenn eine aktive Kategorie ausgewählt wurde.']; 25 | $GLOBALS['TL_LANG']['tl_module']['news_resetCategories'] = ['Kategorie-Filter zurücksetzen', 'Fügt einen Link hinzu, um die Filter zurückzusetzen.']; 26 | $GLOBALS['TL_LANG']['tl_module']['news_showEmptyCategories'] = ['Leere Kategorien anzeigen', 'Zeigt Kategorien auch dann an, wenn die Kategorie keine Nachrichtenbeiträge enthält.']; 27 | $GLOBALS['TL_LANG']['tl_module']['news_forceCategoryUrl'] = ['Kategorie-URL erzwingen', 'Nutzt die Zielseite der Kategorie (falls gesetzt) statt die reguläre Filter-URL.']; 28 | $GLOBALS['TL_LANG']['tl_module']['news_categoriesRoot'] = ['Referenz-Kategorie (Root)', 'Hier kann die Referenzkategorie ausgewählt werden. Diese wird als Startpunkt benutzt (ähnlich zum Navigationsmodul).']; 29 | $GLOBALS['TL_LANG']['tl_module']['news_categoryFilterPage'] = ['Kategorie-Zielseite', 'Hier kann die Zielseite für die Kategorie-Filterung festgelegt werden. Dies überschreibt die Kategorie-Zielseite.']; 30 | $GLOBALS['TL_LANG']['tl_module']['news_categoryImgSize'] = ['Bildgröße für Nachrichten-Kategorien', &$GLOBALS['TL_LANG']['tl_module']['imgSize'][1]]; 31 | 32 | /* 33 | * Reference. 34 | */ 35 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategoriesOrderRef'] = [ 36 | 'default' => 'Standardsortierung', 37 | 'best_match' => 'Beste Übereinstimmung', 38 | ]; 39 | -------------------------------------------------------------------------------- /contao/languages/de/tl_news.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news']['categories'] = ['Kategorien', 'Eine oder mehrere Kategorien auswählen.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news']['category_legend'] = 'Nachrichten-Kategorien'; 20 | -------------------------------------------------------------------------------- /contao/languages/de/tl_news_archive.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Global operations. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Kategorien', 'Nachrichten-Kategorien hinzufügen und bearbeiten.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news_archive']['categories_legend'] = 'Kategorien-Einstellungen'; 20 | 21 | /* 22 | * Fields 23 | */ 24 | $GLOBALS['TL_LANG']['tl_news_archive']['limitCategories'] = ['Kategorien-Auswahl einschränken', 'Verfügbare Kategorien für dieses Archiv einstellen.']; 25 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Nachrichten-Kategorien', 'Bitte wähle eine oder mehrere Nachrichten-Kategorien.']; 26 | -------------------------------------------------------------------------------- /contao/languages/de/tl_news_category.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_category']['title'] = ['Titel', 'Bitte Kategorie-Titel eingeben.']; 15 | $GLOBALS['TL_LANG']['tl_news_category']['frontendTitle'] = ['Frontend Titel', 'Titel der im Frontend angezeigt wird.']; 16 | $GLOBALS['TL_LANG']['tl_news_category']['alias'] = ['Nachrichten-Kategorie-Alias', 'Der Kategorie-Alias ist eine eindeutige Referenz, die anstelle der numerischen ID aufgerufen werden kann.']; 17 | $GLOBALS['TL_LANG']['tl_news_category']['cssClass'] = ['CSS-Klasse', 'Hier kann eine CSS-Klasse vergeben werden, welche zur Kategorie im Front-End hinzugefügt wird.']; 18 | $GLOBALS['TL_LANG']['tl_news_category']['description'] = ['Beschreibung', 'Hier können Sie eine Beschreibung der Kategorie eingeben.']; 19 | $GLOBALS['TL_LANG']['tl_news_category']['image'] = ['Bild', 'Hier können Sie das Bild für die Kategorie auswählen.']; 20 | $GLOBALS['TL_LANG']['tl_news_category']['hideInList'] = ['Im Listen/Archiv-Modul verstecken', 'Zeigt diese Kategorie nicht im Nachrichtenliste oder -archiv Modul an (wirkt sich nur auf die news_ Templates aus).']; 21 | $GLOBALS['TL_LANG']['tl_news_category']['hideInReader'] = ['Im Leser-Modul verstecken', 'Zeigt diese Kategorie nicht im Nachrichtenleser-Modul an (wirkt sich nur auf die news_ Templates aus).']; 22 | $GLOBALS['TL_LANG']['tl_news_category']['excludeInRelated'] = ['Aus ähnlicher Nachrichtenliste ausschließen', 'Schließt die Nachrichten dieser Kategorie in der Nachrichten-Liste der ähnlichen Nachrichten aus.']; 23 | $GLOBALS['TL_LANG']['tl_news_category']['jumpTo'] = ['Weiterleitungsseite', 'Hier kann eine Seite ausgewählt werden, auf diese ein Besucher weitergeleitet wird wenn ein Kategorielink im Nachrichtentemplate angeklickt wird.']; 24 | $GLOBALS['TL_LANG']['tl_news_category']['published'] = ['Kategorie veröffentlichen', 'Nachrichten-Kategorie veröffentlichen.']; 25 | 26 | /* 27 | * Legends 28 | */ 29 | $GLOBALS['TL_LANG']['tl_news_category']['title_legend'] = 'Titel und Alias'; 30 | $GLOBALS['TL_LANG']['tl_news_category']['details_legend'] = 'Kategorie Details'; 31 | $GLOBALS['TL_LANG']['tl_news_category']['modules_legend'] = 'Modul-Einstellungen'; 32 | $GLOBALS['TL_LANG']['tl_news_category']['redirect_legend'] = 'Weiterleitungs-Einstellungen'; 33 | $GLOBALS['TL_LANG']['tl_news_category']['publish_legend'] = 'Veröffentlichung'; 34 | 35 | /* 36 | * Buttons 37 | */ 38 | $GLOBALS['TL_LANG']['tl_news_category']['new'] = ['Nachrichten-Kategorie', 'Nachrichten-Kategorie erstellen']; 39 | $GLOBALS['TL_LANG']['tl_news_category']['show'] = ['Details der Kategorie', 'Zeigt die Details der Kategorie ID %s']; 40 | $GLOBALS['TL_LANG']['tl_news_category']['edit'] = ['Kategorie bearbeiten', 'Kategorie ID %s bearbeiten']; 41 | $GLOBALS['TL_LANG']['tl_news_category']['cut'] = ['Kategorie verschieben', 'Kategorie ID %s verschieben']; 42 | $GLOBALS['TL_LANG']['tl_news_category']['copy'] = ['Kategorie kopieren', 'Kopiert die Kategorie ID %s']; 43 | $GLOBALS['TL_LANG']['tl_news_category']['copyChilds'] = ['Unterkategorien kopieren', 'Kopiert die Kategorie ID %s mit den Unterkategorien']; 44 | $GLOBALS['TL_LANG']['tl_news_category']['delete'] = ['Kategorie löschen', 'Löscht die Kategorie ID %s']; 45 | $GLOBALS['TL_LANG']['tl_news_category']['toggle'] = ['Kategorie veröffentlichen/unveröffentlichen', 'Kategorie ID %s veröffentlichen/unveröffentlichen']; 46 | $GLOBALS['TL_LANG']['tl_news_category']['pasteafter'] = ['Einfügen nach', 'Nach Kategorie ID %s einfügen']; 47 | $GLOBALS['TL_LANG']['tl_news_category']['pasteinto'] = ['Einfügen in', 'In Kategorie ID %s einfügen']; 48 | -------------------------------------------------------------------------------- /contao/languages/de/tl_page.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_page']['newsCategories'] = ['Kategorien', 'Eine oder mehrere Kategorien auswählen.']; 15 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show'] = ['Kategorien im Feed anzeigen', 'Hier können die Kategorien ausgewählt werden, die im Feed angezeigt werden sollen.']; 16 | 17 | /* 18 | * Reference 19 | */ 20 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['empty'] = 'Nicht anzeigen'; 21 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['title'] = 'Im Titel anzeigen'; 22 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_before'] = 'Vor dem Inhalt anzeigen'; 23 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_after'] = 'Nach dem Inhalt anzeigen'; 24 | -------------------------------------------------------------------------------- /contao/languages/de/tl_settings.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | $GLOBALS['TL_LANG']['tl_settings']['news_categories_legend'] = 'Nachrichten-Kategorien'; 12 | -------------------------------------------------------------------------------- /contao/languages/de/tl_user.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_user']['newscategories'] = ['Nachrichten-Kategorien verwalten', 'Benutzer erlauben, Kategorien zu verwalten']; 15 | $GLOBALS['TL_LANG']['tl_user']['newscategories_default'] = ['Standard-Nachrichten-Kategorien', 'Hier können die Standard-Nachrichten-Kategorien gesetzt werden, die verwendet werden, wenn der Benutzer keine Berechtigung hat um Kategorien zu bearbeiten.']; 16 | $GLOBALS['TL_LANG']['tl_user']['newscategories_roots'] = ['Nachrichten-Kategorie-Startpunkte', 'Hier können die Nachrichten-Kategorien als Startpunkte ausgewählt werden, auf die der Benutzer Zugriff haben soll. Unterkategorien sind damit automatisch ausgewählt!']; 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_user']['newsCategories_legend'] = 'Berechtigungen Nachrichten-Kategorien'; 22 | 23 | /* 24 | * Reference 25 | */ 26 | $GLOBALS['TL_LANG']['tl_user']['newscategoriesRef'] = ['manage' => 'Verwalten']; 27 | -------------------------------------------------------------------------------- /contao/languages/de/tl_user_group.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\System; 12 | 13 | /* 14 | * Load tl_user language file. 15 | */ 16 | System::loadLanguageFile('tl_user'); 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_user_group']['newsCategories_legend'] = $GLOBALS['TL_LANG']['tl_user']['newsCategories_legend']; 22 | -------------------------------------------------------------------------------- /contao/languages/en/default.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Miscellaneous. 13 | */ 14 | $GLOBALS['TL_LANG']['MSC']['newsCategories'] = 'Categories:'; 15 | $GLOBALS['TL_LANG']['MSC']['resetCategories'] = ['All categories', 'Show news from all categories']; 16 | $GLOBALS['TL_LANG']['MSC']['resetCategoriesCumulative'] = ['Reset categories', 'Show news from all categories']; 17 | $GLOBALS['TL_LANG']['MSC']['activeCategories'] = 'Active categories:'; 18 | $GLOBALS['TL_LANG']['MSC']['inactiveCategories'] = 'Filter by categories:'; 19 | $GLOBALS['TL_LANG']['MSC']['inactiveCategoriesAdd'] = 'Add another category to filter:'; 20 | 21 | /* 22 | * Content elements 23 | */ 24 | $GLOBALS['TL_LANG']['CTE']['newsfilter'] = ['News module filtered', 'Adds a news module to the page with custom filter settings.']; 25 | -------------------------------------------------------------------------------- /contao/languages/en/modules.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Front end modules. 13 | */ 14 | $GLOBALS['TL_LANG']['FMD']['newscategories'] = ['News categories list', 'Adds a categories list to the page.']; 15 | $GLOBALS['TL_LANG']['FMD']['newscategories_cumulative'] = ['News categories cumulative filter', 'Adds a cumulative filter of categories to the page.']; 16 | $GLOBALS['TL_LANG']['FMD']['newscategories_cumulativehierarchical'] = ['News categories cumulative hierarchical filter', 'Adds a cumulative hierarchical filter of categories to the page.']; 17 | -------------------------------------------------------------------------------- /contao/languages/en/tl_module.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_module']['news_categories'] = ['News categories', 'Please choose the news categories.']; 15 | $GLOBALS['TL_LANG']['tl_module']['news_customCategories'] = ['Limit categories display', 'Choose which categories should be displayed.']; 16 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategories'] = ['Filter by categories', 'Filter the news list by the categories list module.']; 17 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategoriesCumulative'] = ['Filter by categories (cumulative)', 'Filter the news list by the cumulative filter module. Takes priority over the previous checkbox for filter.']; 18 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategories'] = ['Related categories mode', 'Use categories of the current news item to filter by. Note: the module must be on the same page as news reader module.']; 19 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategoriesOrder'] = ['Related categories order', 'Here you can choose the related categories order.']; 20 | $GLOBALS['TL_LANG']['tl_module']['news_includeSubcategories'] = ['Include the subcategories', 'Include the subcategories in the filtering if the parent category is active.']; 21 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategoriesUnion'] = ['Filter by categories using union (cumulative only)', 'Filter news and categories using union (x OR y) instead of intersection (x AND y).']; 22 | $GLOBALS['TL_LANG']['tl_module']['news_enableCanonicalUrls'] = ['Enable canonical URLs', 'Adds the canonical URL tag when active category is present.']; 23 | $GLOBALS['TL_LANG']['tl_module']['news_filterDefault'] = ['Default filter', 'Here you can choose the default filter that will be applied to the newslist.']; 24 | $GLOBALS['TL_LANG']['tl_module']['news_filterPreserve'] = ['Preserve the default filter', 'Preserve the default filter settings even if there is an active category selected.']; 25 | $GLOBALS['TL_LANG']['tl_module']['news_resetCategories'] = ['Reset categories link', 'Add a link to reset categories filter.']; 26 | $GLOBALS['TL_LANG']['tl_module']['news_showEmptyCategories'] = ['Show empty categories', 'Show the categories even if they do not contain any news entries.']; 27 | $GLOBALS['TL_LANG']['tl_module']['news_forceCategoryUrl'] = ['Force category URL', 'Use the category target page URL (if available) instead of the regular filter-link.']; 28 | $GLOBALS['TL_LANG']['tl_module']['news_categoriesRoot'] = ['Reference category (root)', 'Here you can choose the reference category. It will be used as the starting point (similar to navigation module).']; 29 | $GLOBALS['TL_LANG']['tl_module']['news_categoryFilterPage'] = ['Category target page', 'Here you can choose the news category target page that will override the category URLs with a filter-link to this page.']; 30 | $GLOBALS['TL_LANG']['tl_module']['news_categoryImgSize'] = ['News category image size', &$GLOBALS['TL_LANG']['tl_module']['imgSize'][1]]; 31 | 32 | /* 33 | * Reference. 34 | */ 35 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategoriesOrderRef'] = [ 36 | 'default' => 'Default order', 37 | 'best_match' => 'Best match', 38 | ]; 39 | -------------------------------------------------------------------------------- /contao/languages/en/tl_news.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news']['categories'] = ['Categories', 'Here you can select one or more news categories.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news']['category_legend'] = 'News categories'; 20 | -------------------------------------------------------------------------------- /contao/languages/en/tl_news_archive.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Global operations. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Categories', 'Add and manage news categories.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news_archive']['categories_legend'] = 'Categories settings'; 20 | 21 | /* 22 | * Fields 23 | */ 24 | $GLOBALS['TL_LANG']['tl_news_archive']['limitCategories'] = ['Limit categories', 'Limit the available categories for this archive.']; 25 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['News categories', 'Please choose one or more news categories.']; 26 | -------------------------------------------------------------------------------- /contao/languages/en/tl_news_category.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_category']['title'] = ['Title', 'Please enter the category title.']; 15 | $GLOBALS['TL_LANG']['tl_news_category']['frontendTitle'] = ['Frontend title', 'Here you can enter the category title that will be displayed in front end.']; 16 | $GLOBALS['TL_LANG']['tl_news_category']['alias'] = ['Category alias', 'The category alias is a unique reference to the category which can be called instead of its numeric ID.']; 17 | $GLOBALS['TL_LANG']['tl_news_category']['cssClass'] = ['CSS class', 'Here you can enter the CSS class that will be added to the category in front end.']; 18 | $GLOBALS['TL_LANG']['tl_news_category']['description'] = ['Description', 'Here you can enter the category description.']; 19 | $GLOBALS['TL_LANG']['tl_news_category']['image'] = ['Image', 'Here you can choose the category image.']; 20 | $GLOBALS['TL_LANG']['tl_news_category']['hideInList'] = ['Hide in list/archive module', 'Do not display category in the news list/archive module (affects only the news_ templates).']; 21 | $GLOBALS['TL_LANG']['tl_news_category']['hideInReader'] = ['Hide in reader module', 'Do not display category in the news reader module (affects only the news_ templates).']; 22 | $GLOBALS['TL_LANG']['tl_news_category']['excludeInRelated'] = ['Exclude in related news list', 'Exclude the news of this category in the related list module.']; 23 | $GLOBALS['TL_LANG']['tl_news_category']['jumpTo'] = ['Redirect page', 'Here you can choose the page to which visitors will be redirected when clicking a category link in the news template or categories list module (configurable). This setting is inherited!']; 24 | $GLOBALS['TL_LANG']['tl_news_category']['published'] = ['Publish category', 'Make the news category publicly visible on the website.']; 25 | 26 | /* 27 | * Legends 28 | */ 29 | $GLOBALS['TL_LANG']['tl_news_category']['title_legend'] = 'Title and alias'; 30 | $GLOBALS['TL_LANG']['tl_news_category']['details_legend'] = 'Category details'; 31 | $GLOBALS['TL_LANG']['tl_news_category']['modules_legend'] = 'Modules settings'; 32 | $GLOBALS['TL_LANG']['tl_news_category']['redirect_legend'] = 'Redirect settings'; 33 | $GLOBALS['TL_LANG']['tl_news_category']['publish_legend'] = 'Publish settings'; 34 | 35 | /* 36 | * Buttons 37 | */ 38 | $GLOBALS['TL_LANG']['tl_news_category']['new'] = ['New category', 'Create a new category']; 39 | $GLOBALS['TL_LANG']['tl_news_category']['show'] = ['Category details', 'Show the details of category ID %s']; 40 | $GLOBALS['TL_LANG']['tl_news_category']['edit'] = ['Edit category', 'Edit category ID %s']; 41 | $GLOBALS['TL_LANG']['tl_news_category']['cut'] = ['Move category', 'Move category ID %s']; 42 | $GLOBALS['TL_LANG']['tl_news_category']['copy'] = ['Duplicate category', 'Duplicate category ID %s']; 43 | $GLOBALS['TL_LANG']['tl_news_category']['copyChilds'] = ['Duplicate with subcategories', 'Duplicate category ID %s with its subcategories']; 44 | $GLOBALS['TL_LANG']['tl_news_category']['delete'] = ['Delete category', 'Delete category ID %s']; 45 | $GLOBALS['TL_LANG']['tl_news_category']['toggle'] = ['Publish/unpublish category', 'Publish/unpublish category ID %s']; 46 | $GLOBALS['TL_LANG']['tl_news_category']['pasteafter'] = ['Paste after', 'Paste after category ID %s']; 47 | $GLOBALS['TL_LANG']['tl_news_category']['pasteinto'] = ['Paste into', 'Paste into category ID %s']; 48 | -------------------------------------------------------------------------------- /contao/languages/en/tl_page.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_param'] = ['Custom URL parameter name', 'Here you can enter a custom parameter name that is used in URL. For example, by entering "kategorie" you can change the default /category/music.html to /kategorie/music.html.']; 15 | $GLOBALS['TL_LANG']['tl_page']['newsCategories'] = ['Categories', 'Here you can select one or more news categories.']; 16 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show'] = ['Show categories in the feed', 'Here you can choose how the news categories will be showed in the feed.']; 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_legend'] = 'News categories settings'; 22 | 23 | /* 24 | * Reference 25 | */ 26 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['empty'] = 'Do not show'; 27 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['title'] = 'Show in title'; 28 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_before'] = 'Show before the text'; 29 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_after'] = 'Show after the text'; 30 | -------------------------------------------------------------------------------- /contao/languages/en/tl_settings.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\System; 12 | 13 | System::loadLanguageFile('tl_page'); 14 | 15 | $GLOBALS['TL_LANG']['tl_settings']['news_categories_legend'] = 'News categories'; 16 | $GLOBALS['TL_LANG']['tl_settings']['news_categorySlugSetting'] = &$GLOBALS['TL_LANG']['tl_page']['validAliasCharacters']; 17 | -------------------------------------------------------------------------------- /contao/languages/en/tl_user.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_user']['newscategories'] = ['News categories permissions', 'Here you can set the news categories permissions.']; 15 | $GLOBALS['TL_LANG']['tl_user']['newscategories_default'] = ['Default news categories', 'Here you can set the default news categories that will be used if the user has no permissions to edit the categories field.']; 16 | $GLOBALS['TL_LANG']['tl_user']['newscategories_roots'] = ['News category roots', 'Here you can limit the news category roots the user has access to. Note that the subcategories will included automatically!']; 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_user']['newsCategories_legend'] = 'News categories permissions'; 22 | 23 | /* 24 | * Reference 25 | */ 26 | $GLOBALS['TL_LANG']['tl_user']['newscategoriesRef'] = ['manage' => 'Manage']; 27 | -------------------------------------------------------------------------------- /contao/languages/en/tl_user_group.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\System; 12 | 13 | /* 14 | * Load tl_user language file. 15 | */ 16 | System::loadLanguageFile('tl_user'); 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_user_group']['newsCategories_legend'] = $GLOBALS['TL_LANG']['tl_user']['newsCategories_legend']; 22 | -------------------------------------------------------------------------------- /contao/languages/fr/default.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Miscellaneous. 13 | */ 14 | $GLOBALS['TL_LANG']['MSC']['newsCategories'] = 'Catégories :'; 15 | $GLOBALS['TL_LANG']['MSC']['resetCategories'] = ['Toutes les catégories', 'Afficher les actualités de toutes les catégories']; 16 | 17 | /* 18 | * Content elements 19 | */ 20 | $GLOBALS['TL_LANG']['CTE']['newsfilter'] = ['Module d\'actualités filtré', 'Ajoute un module d\'actualités à la page avec des paramètres de filtres personnalisés.']; 21 | -------------------------------------------------------------------------------- /contao/languages/fr/modules.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Front end modules. 13 | */ 14 | $GLOBALS['TL_LANG']['FMD']['newscategories'] = ['Liste des catégories d\'actualités', 'Ajoute une liste des catégories à la page.']; 15 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_module.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_module']['news_categories'] = ['Catégories des actualités', 'Veuillez, s\'il vous plaît, choisir les catégories d\'actualités.']; 15 | $GLOBALS['TL_LANG']['tl_module']['news_customCategories'] = ['Limiter l\'affichage des catégories', 'Choisir les catégories qui doivent être affichées.']; 16 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategories'] = ['Filtrer par catégorie', 'Filtrer la liste des actualités par le module liste des catégories.']; 17 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategories'] = ['Mode catégories apparentées', 'Utilisez les catégories de l\'actualité courante pour filtrer par. Remarque : le module doit être sur la même page que le module lecteur d\'actualités.']; 18 | $GLOBALS['TL_LANG']['tl_module']['news_filterDefault'] = ['Filtre par défaut', 'Ici, vous pouvez choisir le filtre par défaut qui sera appliqué à la liste des actualités.']; 19 | $GLOBALS['TL_LANG']['tl_module']['news_filterPreserve'] = ['Préserver le filtre par défaut', 'Conserver les paramètres de filtrage par défaut, même s\'il y a une catégorie active sélectionnée.']; 20 | $GLOBALS['TL_LANG']['tl_module']['news_resetCategories'] = ['Lien "Toutes les catégories"', 'Ajouter un lien pour remettre le filtre dans son état initial.']; 21 | $GLOBALS['TL_LANG']['tl_module']['news_categoriesRoot'] = ['Catégorie de référence (racine)', 'Ici, vous pouvez choisir la catégorie de référence. Elle sera utilisée comme point de départ (semblable au module de navigation).']; 22 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_news.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news']['categories'] = ['Catégories', 'Ici, vous pouvez sélectionner une ou plusieurs catégories d\'actualités.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news']['category_legend'] = 'Catégories d\'actualités'; 20 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_news_archive.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Global operations. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Catégories', 'Ajouter et gérer des catégories d\'actualités']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news_archive']['categories_legend'] = 'Paramètres des catégories'; 20 | 21 | /* 22 | * Fields 23 | */ 24 | $GLOBALS['TL_LANG']['tl_news_archive']['limitCategories'] = ['Limiter les catégories', 'Limiter les catégories disponibles pour cette archive.']; 25 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Catégories d\'actualités', 'Veuillez, s\'il vous plaît, sélectionner une ou plusieurs catégories d\'actualités.']; 26 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_news_category.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_category']['title'] = ['Titre', 'Veuillez, s\'il vous plaît, saisir un titre pour cette catégorie.']; 15 | $GLOBALS['TL_LANG']['tl_news_category']['frontendTitle'] = ['Titre front office', 'Ici, vous pouvez saisir un titre de catégorie qui sera affiché en front office.']; 16 | $GLOBALS['TL_LANG']['tl_news_category']['alias'] = ['Alias', 'L\'alias de la catégorie est une référence unique qui peut être utilisé à la place de son ID numérique.']; 17 | $GLOBALS['TL_LANG']['tl_news_category']['cssClass'] = ['Classe CSS', 'Ici, vous pouvez saisir une classe CSS qui sera ajoutée à la catégorie dans le front office']; 18 | $GLOBALS['TL_LANG']['tl_news_category']['jumpTo'] = ['Page de redirection', 'Ici, vous pouvez choisir la page à laquelle les visiteurs seront redirigés lorsque vous cliquez sur un lien de catégorie dans le modèle d\'actualité.']; 19 | $GLOBALS['TL_LANG']['tl_news_category']['published'] = ['Publier la catégorie', 'Rendre la catégorie visible sur le site internet.']; 20 | 21 | /* 22 | * Legends 23 | */ 24 | $GLOBALS['TL_LANG']['tl_news_category']['title_legend'] = 'Titre et alias'; 25 | $GLOBALS['TL_LANG']['tl_news_category']['redirect_legend'] = 'Paramètres de redirection'; 26 | $GLOBALS['TL_LANG']['tl_news_category']['publish_legend'] = 'Paramètres de publication'; 27 | 28 | /* 29 | * Buttons 30 | */ 31 | $GLOBALS['TL_LANG']['tl_news_category']['new'] = ['Nouvelle catégorie', 'Créer une nouvelle catégorie']; 32 | $GLOBALS['TL_LANG']['tl_news_category']['show'] = ['Détails de la catégorie', 'Afficher les détails de la catégorie ID %s']; 33 | $GLOBALS['TL_LANG']['tl_news_category']['edit'] = ['Éditer la catégorie', 'Éditer la catégorie ID %s']; 34 | $GLOBALS['TL_LANG']['tl_news_category']['cut'] = ['Déplacer la catégorie', 'Déplacer la catégorie ID %s']; 35 | $GLOBALS['TL_LANG']['tl_news_category']['copy'] = ['Dupliquer la catégorie', 'Dupliquer la catégorie ID %s']; 36 | $GLOBALS['TL_LANG']['tl_news_category']['copyChilds'] = ['Dupliquer avec les sous-catégories', 'Dupliquer la catégorie ID %s avec ses sous-catégories']; 37 | $GLOBALS['TL_LANG']['tl_news_category']['delete'] = ['Supprimer la catégorie', 'Supprimer la catégorie ID %s']; 38 | $GLOBALS['TL_LANG']['tl_news_category']['toggle'] = ['Publier/Dépublier cette catégorie', 'Publier/Dépublier la catégorie ID %s']; 39 | $GLOBALS['TL_LANG']['tl_news_category']['pasteafter'] = ['Coller après', 'Coller après la catégorie ID %s']; 40 | $GLOBALS['TL_LANG']['tl_news_category']['pasteinto'] = ['Coller dedans', 'Coller dedans la catégorie ID %s']; 41 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_page.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_param'] = ['Nom du paramètre personnalisé de l\'URL', 'Ici, vous pouvez saisir un nom du paramètre personnalisé qui est utilisé dans l\'URL. Par exemple, en saisissant "kategorie" vous pouvez changer /category/music.html (par défaut) par /kategorie/music.html.']; 15 | $GLOBALS['TL_LANG']['tl_page']['newsCategories'] = ['Catégories', 'Ici, vous pouvez sélectionner une ou plusieurs catégories d\'actualités.']; 16 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show'] = ['Afficher les catégories dans le flux', 'Ici, vous pouvez choisir comment les catégories d\'actualités seront affichées dans le flux.']; 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_legend'] = 'Paramètres des catégories d\'actualités'; 22 | 23 | /* 24 | * Reference 25 | */ 26 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['empty'] = 'Ne pas afficher'; 27 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['title'] = 'Afficher dans le titre'; 28 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_before'] = 'Afficher avant le texte'; 29 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_after'] = 'Afficher après le texte'; 30 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_user.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_user']['newscategories'] = ['Gérer les catégories d\'actualités', 'Permettre à l\'utilisateur de gérer les catégories.']; 15 | $GLOBALS['TL_LANG']['tl_user']['newscategories_default'] = ['Catégories d\'actualités par défaut', 'Ici, vous pouvez définir les catégories d\'actualités par défaut qui seront utilisées si l\'utilisateur n\'a pas les autorisations pour modifier le champ des catégories.']; 16 | 17 | /* 18 | * Legends 19 | */ 20 | $GLOBALS['TL_LANG']['tl_user']['newsCategories_legend'] = 'Permissions des catégories d\'actualités'; 21 | -------------------------------------------------------------------------------- /contao/languages/fr/tl_user_group.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | use Contao\System; 12 | 13 | /* 14 | * Load tl_user language file. 15 | */ 16 | System::loadLanguageFile('tl_user'); 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_user_group']['newsCategories_legend'] = $GLOBALS['TL_LANG']['tl_user']['newsCategories_legend']; 22 | -------------------------------------------------------------------------------- /contao/languages/it/default.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Miscellaneous. 13 | */ 14 | $GLOBALS['TL_LANG']['MSC']['resetCategories'] = ['Tutte le categorie', 'Mostra tutte le news']; 15 | -------------------------------------------------------------------------------- /contao/languages/it/modules.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Front end modules. 13 | */ 14 | $GLOBALS['TL_LANG']['FMD']['newscategories'] = ['Lista categorie news', 'Aggiunge una lista di categorie news alla pagina.']; 15 | -------------------------------------------------------------------------------- /contao/languages/it/tl_module.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_module']['news_categories'] = ['Categorie news', 'Scegli le categorie di news.']; 15 | $GLOBALS['TL_LANG']['tl_module']['news_customCategories'] = ['Categorie personalizzate', 'Mostra solo le categorie scelte.']; 16 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategories'] = ['Filtra per categorie', 'Filtra questa lista con il modulo Categorie News.']; 17 | $GLOBALS['TL_LANG']['tl_module']['news_filterDefault'] = ['Filtro predefinito', 'Puoi scegliere con quali categorie filtrare la lista di news.']; 18 | $GLOBALS['TL_LANG']['tl_module']['news_filterPreserve'] = ['Mantieni il filtro default', 'Mantiene il filtro attivo anche se si seleziona una categoria.']; 19 | $GLOBALS['TL_LANG']['tl_module']['news_resetCategories'] = ['Link di reset', 'Aggiunge un link per resettare il filtro applicato alla lista news.']; 20 | -------------------------------------------------------------------------------- /contao/languages/it/tl_news.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news']['categories'] = ['Categorie', 'Puoi selezionare una o più categorie.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news']['category_legend'] = 'Categorie News'; 20 | -------------------------------------------------------------------------------- /contao/languages/it/tl_news_archive.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Global operations. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Categorie', 'Aggiungi e gestisci le categorie di news.']; 15 | -------------------------------------------------------------------------------- /contao/languages/it/tl_news_category.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_category']['title'] = ['Titolo', 'Nome della categoria per esteso.']; 15 | $GLOBALS['TL_LANG']['tl_news_category']['frontendTitle'] = ['Titolo in Frontend', 'Puoi scegliere un titolo diverso da far apparire in front end.']; 16 | $GLOBALS['TL_LANG']['tl_news_category']['alias'] = ['Alias', 'Testo univoco per il link, che può essere usato al posto dell\'ID.']; 17 | $GLOBALS['TL_LANG']['tl_news_category']['published'] = ['Pubblica categoria', 'Rende attiva la categoria nel sito web.']; 18 | 19 | /* 20 | * Legends 21 | */ 22 | $GLOBALS['TL_LANG']['tl_news_category']['title_legend'] = 'Titolo e alias'; 23 | $GLOBALS['TL_LANG']['tl_news_category']['publish_legend'] = 'Parametri di pubblicazione'; 24 | 25 | /* 26 | * Buttons 27 | */ 28 | $GLOBALS['TL_LANG']['tl_news_category']['new'] = ['Nuova categoria', 'Crea una nuova categoria di news']; 29 | $GLOBALS['TL_LANG']['tl_news_category']['show'] = ['Dettagli categoria', 'Mostra i dettagli della categoria ID %s']; 30 | $GLOBALS['TL_LANG']['tl_news_category']['edit'] = ['Modifica categoria', 'Modifica la categoria ID %s']; 31 | $GLOBALS['TL_LANG']['tl_news_category']['copy'] = ['Duplica categoria', 'Duplica la categoria ID %s']; 32 | $GLOBALS['TL_LANG']['tl_news_category']['delete'] = ['Elimina categoria', 'Elimina la categoria ID %s']; 33 | $GLOBALS['TL_LANG']['tl_news_category']['toggle'] = ['Pubblicazione categoria', 'Attiva/disattiva la categoria ID %s']; 34 | -------------------------------------------------------------------------------- /contao/languages/it/tl_page.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_page']['newsCategories'] = ['Categorie', 'Puoi selezionare una o più categorie.']; 15 | -------------------------------------------------------------------------------- /contao/languages/it/tl_user.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_user']['newscategories'] = ['Gestione delle categorie news', 'Permette all\'utente di aggiungere, modificare o eliminare categorie di news.']; 15 | -------------------------------------------------------------------------------- /contao/languages/pl/default.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Miscellaneous. 13 | */ 14 | $GLOBALS['TL_LANG']['MSC']['newsCategories'] = 'Kategorie:'; 15 | $GLOBALS['TL_LANG']['MSC']['resetCategories'] = ['Wszystkie', 'Pokaż aktualności z wszystkich kategorii']; 16 | 17 | /* 18 | * Content elements 19 | */ 20 | $GLOBALS['TL_LANG']['CTE']['newsfilter'] = ['Moduł aktualności filtrowany', 'Dodaje moduł aktualności do strony z własnymi ustawieniami filtrowania.']; 21 | -------------------------------------------------------------------------------- /contao/languages/pl/modules.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Front end modules. 13 | */ 14 | $GLOBALS['TL_LANG']['FMD']['newscategories'] = ['Kategorie aktualności', 'Dodaje do strony listę kategorii aktualności.']; 15 | -------------------------------------------------------------------------------- /contao/languages/pl/tl_module.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_module']['news_categories'] = ['Kategorie aktualności', 'Wybierz kategorie aktualności.']; 15 | $GLOBALS['TL_LANG']['tl_module']['news_customCategories'] = ['Ogranicz wyświetlanie kategorii', 'Wybierz, które kategorie mają zostać wyświetlone.']; 16 | $GLOBALS['TL_LANG']['tl_module']['news_filterCategories'] = ['Filtruj przez kategorie', 'Filtruj listę aktualności przez moduł listy kategorii.']; 17 | $GLOBALS['TL_LANG']['tl_module']['news_relatedCategories'] = ['Tryb powiązanych kategorii', 'Użyj kategorii aktywnej aktualności do filtrowania. Uwaga: moduł musi być na tej samej stronie co moduł czytnika aktualności.']; 18 | $GLOBALS['TL_LANG']['tl_module']['news_filterDefault'] = ['Domyślny filtr', 'Tutaj możesz wybrać domyślny filtr, który będzie użyty przez listę aktualności.']; 19 | $GLOBALS['TL_LANG']['tl_module']['news_filterPreserve'] = ['Zachowaj domyślny filtr', 'Zachowaj domyślny filtr, gdy aktywna jest inna kategoria.']; 20 | $GLOBALS['TL_LANG']['tl_module']['news_resetCategories'] = ['Link resetujący kategorię', 'Dodaj do strony link resetujący filtr kategorii.']; 21 | $GLOBALS['TL_LANG']['tl_module']['news_categoriesRoot'] = ['Kategoria startowa', 'Tutaj możesz wybrać kategorię startową. Zostanie ona użyta jako punkty startowy (podobnie do modułu nawigacji).']; 22 | -------------------------------------------------------------------------------- /contao/languages/pl/tl_news.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news']['categories'] = ['Kategorie', 'Tutaj możesz wybrać jedną lub więcej kategorii.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news']['category_legend'] = 'Kategorie aktualności'; 20 | -------------------------------------------------------------------------------- /contao/languages/pl/tl_news_archive.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Global operations. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Kategorie', 'Dodawaj i zarządaj kategoriami aktualności.']; 15 | 16 | /* 17 | * Legends 18 | */ 19 | $GLOBALS['TL_LANG']['tl_news_archive']['categories_legend'] = 'Ustawienia kategorii'; 20 | 21 | /* 22 | * Fields 23 | */ 24 | $GLOBALS['TL_LANG']['tl_news_archive']['limitCategories'] = ['Ogranicz kategorie', 'Ogranicz dostępne kategorie dla tego archiwum aktualności.']; 25 | $GLOBALS['TL_LANG']['tl_news_archive']['categories'] = ['Kategorie aktualności', 'Wybierz jedną lub więcej kategorii aktualności.']; 26 | -------------------------------------------------------------------------------- /contao/languages/pl/tl_news_category.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_news_category']['title'] = ['Tytuł', 'Wprowadź tytuł kategorii.']; 15 | $GLOBALS['TL_LANG']['tl_news_category']['frontendTitle'] = ['Tytuł na stronie', 'Tutaj możesz wprowadzić tytuł kategorii, który będzie widoczny na stronie.']; 16 | $GLOBALS['TL_LANG']['tl_news_category']['alias'] = ['Alias kategorii', 'Alias kategorii jest unikalnym odwołaniem do kategorii, do którego można się odwołać zamiast numerycznego ID.']; 17 | $GLOBALS['TL_LANG']['tl_news_category']['cssClass'] = ['Klasa CSS', 'Tutaj możesz wprowadzić klasę CSS, która będzie dodana we front endzie.']; 18 | $GLOBALS['TL_LANG']['tl_news_category']['jumpTo'] = ['Strona przekierowania', 'Tutaj możesz wybrać stronę, na którą zostanie przeniesiony odwiedzający po kliknięciu na link kategorii w szablonie aktualności.']; 19 | $GLOBALS['TL_LANG']['tl_news_category']['published'] = ['Opublikuj kategorię', 'Opublikuj kategorię aktualności na stronie.']; 20 | 21 | /* 22 | * Legends 23 | */ 24 | $GLOBALS['TL_LANG']['tl_news_category']['title_legend'] = 'Tytuł i alias'; 25 | $GLOBALS['TL_LANG']['tl_news_category']['redirect_legend'] = 'Ustawienia przekierowania'; 26 | $GLOBALS['TL_LANG']['tl_news_category']['publish_legend'] = 'Ustawienia publikacji'; 27 | 28 | /* 29 | * Buttons 30 | */ 31 | $GLOBALS['TL_LANG']['tl_news_category']['new'] = ['Nowa kategoria', 'Utwórz nową kategorię']; 32 | $GLOBALS['TL_LANG']['tl_news_category']['show'] = ['Szczegóły kategorii', 'Pokaż szczegóły kategorii ID %s']; 33 | $GLOBALS['TL_LANG']['tl_news_category']['edit'] = ['Edytuj kategorię', 'Edytuj kategorię ID %s']; 34 | $GLOBALS['TL_LANG']['tl_news_category']['copy'] = ['Duplikuj kategorię', 'Duplikuj kategorię ID %s']; 35 | $GLOBALS['TL_LANG']['tl_news_category']['delete'] = ['Usuń kategorię', 'Usuń kategorię ID %s']; 36 | $GLOBALS['TL_LANG']['tl_news_category']['toggle'] = ['Publikuj/ukryj kategorię', 'Publikuj/ukryj kategorię ID %s']; 37 | -------------------------------------------------------------------------------- /contao/languages/pl/tl_page.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_param'] = ['Własna nazwa parametru URL', 'Tutaj możesz wprowadzić własną nazwę parametru, który jest używany w adresie URL. Przykładowo, wprowadzając "kategoria" możesz zmienić domyślny /category/music.html na /kategoria/music.html.']; 15 | $GLOBALS['TL_LANG']['tl_page']['newsCategories'] = ['Kategorie', 'Tutaj możesz wybrać jedną lub więcej kategorii.']; 16 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show'] = ['Pokaż kategorie w kanale', 'Tutaj możesz wybrać w jaki sposób kategorie zostaną pokazane w kanale.']; 17 | 18 | /* 19 | * Legends 20 | */ 21 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_legend'] = 'Ustawienia kategorii aktualności'; 22 | 23 | /* 24 | * Reference 25 | */ 26 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['empty'] = 'Nie pokazuj'; 27 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['title'] = 'Pokaż w tytule'; 28 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_before'] = 'Pokaż przed tekstem'; 29 | $GLOBALS['TL_LANG']['tl_page']['newsCategories_show']['text_after'] = 'Pokaż po tekście'; 30 | -------------------------------------------------------------------------------- /contao/languages/pl/tl_user.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | /* 12 | * Fields. 13 | */ 14 | $GLOBALS['TL_LANG']['tl_user']['newscategories'] = ['Zarządzanie kategoriami aktualności', 'Pozwól użytkownikowi na zarządzanie kategoriami aktualności.']; 15 | -------------------------------------------------------------------------------- /contao/templates/modules/mod_newscategories.html5: -------------------------------------------------------------------------------- 1 | extend('block_unsearchable'); ?> 2 | 3 | block('content'); ?> 4 | 5 | categories ?> 6 | 7 | endblock(); ?> 8 | -------------------------------------------------------------------------------- /contao/templates/modules/mod_newscategories_cumulative.html5: -------------------------------------------------------------------------------- 1 | extend('block_unsearchable'); ?> 2 | 3 | block('content'); ?> 4 | 5 | activeCategories): ?> 6 |
7 |
8 | activeCategories ?> 9 |
10 | 11 | 12 | inactiveCategories): ?> 13 |
14 |
activeCategories ? $GLOBALS['TL_LANG']['MSC']['inactiveCategoriesAdd'] : $GLOBALS['TL_LANG']['MSC']['inactiveCategories'] ?>
15 | inactiveCategories ?> 16 |
17 | 18 | 19 | resetUrl): ?> 20 | 23 | 24 | 25 | endblock(); ?> 26 | -------------------------------------------------------------------------------- /contao/templates/modules/mod_newscategories_cumulativehierarchical.html5: -------------------------------------------------------------------------------- 1 | extend('block_unsearchable'); ?> 2 | 3 | block('content'); ?> 4 | 5 | categories ?> 6 | 7 | endblock(); ?> 8 | -------------------------------------------------------------------------------- /contao/templates/navigation/nav_newscategories.html5: -------------------------------------------------------------------------------- 1 | 2 | 39 | -------------------------------------------------------------------------------- /contao/templates/navigation/nav_newscategories_hierarchical.html5: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # News Categories bundle – Documentation 2 | 3 | 1. [Installation](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration – News Categories bundle 2 | 3 | 1. [Installation](installation.md) 4 | 2. [**Configuration**](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | 11 | 12 | ## Managing categories 13 | 14 | To edit news categories simply go to the news module and click the categories button at the top of the page. 15 | 16 | ![](images/manage.png) 17 | 18 | You will see a sortable tree of categories. It is possible to create an unlimited number of subcategories on different 19 | levels. 20 | 21 | ![](images/category-list.png) 22 | 23 | Every category has its own alias which is a unique reference to the category in the front end. You can also optionally 24 | set the front end title, description and other useful details. Additionally, each entry can be hidden from the public 25 | just like the news articles. 26 | 27 | ![](images/category-edit.png) 28 | 29 | 30 | ## Assign categories 31 | 32 | Once you created some categories, it is time to assign them to news. You can do that by editing the news settings. 33 | Simply use the widget and choose desired categories. Then confirm your choice by clicking the and finally save the news 34 | article. 35 | 36 | ![](images/news-edit.png) 37 | 38 | 39 | ## Advanced configuration 40 | 41 | ### Limit categories per news archive 42 | 43 | To narrow the categories selection individually per every news archive simply set it in the archive settings: 44 | 45 | ![](images/news-archive.png) 46 | 47 | **Note:** this limitation also applies for the administrator users! 48 | -------------------------------------------------------------------------------- /docs/frontend-modules.md: -------------------------------------------------------------------------------- 1 | # Frontend modules – News Categories bundle 2 | 3 | 1. [Installation](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [**Frontend modules**](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | 11 | 12 | ## Frontend modules 13 | 14 | ### News categories list 15 | 16 | The extension comes with an extra front end module that will generate a list of categories as links, which allows users to 17 | filter news items in other news front end modules. 18 | 19 | ![](images/frontend-module.png) 20 | 21 | ### News categories cumulative filter 22 | 23 | For a more advanced filtering similar to a tag cloud, you can use the news categories cumulative filter module. 24 | It displays a flat list of the categories and allows to combine multiple filters. The categories are displayed 25 | as two lists: currently active categories and remaining categories that will still produce some results. 26 | 27 | > **Important:** Make sure to check the `Filter by categories (cumulative)` option in the news list/archive front end module settings 28 | > or the filtering won't work! 29 | 30 | ![](images/cumulative-filter.png) 31 | 32 | By default, the cumulative filter uses an intersection (x AND y) to filter news entries. That means, if you select 33 | categories A and B, then each news must belong to both categories the same time to be displayed. 34 | 35 | In case you would like to display news belonging to either category, select the `Filter by categories using union (cumulative only)` 36 | option in both cumulative filter module and respective news listing module. It will change the filtering behavior to use 37 | a union (x OR y) search instead of intersection. 38 | 39 | ### News categories cumulative hierarchical filter 40 | 41 | As of version 3.4.0, there is a new frontend cumulative hierarchical filter module available. Contrary to the standard 42 | cumulative filter module, it can also display subcategories that can be combined to filter the news list module. 43 | 44 | ### News modules 45 | 46 | The bundle overwrites the default news modules by adding the extra category related features to them. They are fully 47 | optional so your existing modules setup won't be affected until you change their configuration manually. 48 | 49 | > **Important:** Make sure to check the `Filter by categories` option in the news front end module settings 50 | > or the filtering won't work! 51 | 52 | Currently the following news modules are supported: 53 | 54 | - News archive 55 | - News archive menu 56 | - News list 57 | - News reader 58 | 59 | ### Related categories list 60 | 61 | The news list module has also a feature to display the related news items based on the categories of the current item. 62 | This is typically needed when you have a news reader module on the page and below you would like to display similar 63 | news. To achieve this, the module structure on the page has to be the following: 64 | 65 | - news reader module 66 | - news list module (in related categories mode) 67 | 68 | 69 | ## Filter content element 70 | 71 | If you are managing a lot of categories and would like to display the different categories on different pages, 72 | you can have only one news list module and insert it using content element called `News module filtered`. The element 73 | allows you to override the module configuration with your custom one, so there is no longer need to create multiple 74 | list modules. 75 | -------------------------------------------------------------------------------- /docs/images/category-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/category-edit.png -------------------------------------------------------------------------------- /docs/images/category-language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/category-language.png -------------------------------------------------------------------------------- /docs/images/category-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/category-list.png -------------------------------------------------------------------------------- /docs/images/cumulative-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/cumulative-filter.png -------------------------------------------------------------------------------- /docs/images/feeds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/feeds.png -------------------------------------------------------------------------------- /docs/images/frontend-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/frontend-module.png -------------------------------------------------------------------------------- /docs/images/manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/manage.png -------------------------------------------------------------------------------- /docs/images/news-archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/news-archive.png -------------------------------------------------------------------------------- /docs/images/news-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/news-edit.png -------------------------------------------------------------------------------- /docs/images/page-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/docs/images/page-settings.png -------------------------------------------------------------------------------- /docs/insert-tags.md: -------------------------------------------------------------------------------- 1 | # Insert tags – News Categories bundle 2 | 3 | 1. [Installation](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [**Insert tags**](insert-tags.md) 10 | 11 | 12 | ## Insert tags 13 | 14 | You can use the `{{news_categories::*}}` insert tags which will print information about the currently active category. 15 | 16 | Examples: 17 | 18 | ``` 19 | {{news_categories::title}} - prints category title 20 | {{news_categories::frontendTitle}} - prints category frontend title 21 | {{image::{{news_categories::image}}}} - prints category image 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation – News Categories bundle 2 | 3 | 1. [**Installation**](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | 11 | 12 | ## Requirements 13 | 14 | Minimum requirements: 15 | 16 | - PHP 8.1 17 | - [Contao](https://github.com/contao/managed-edition) 5.1 18 | - [Contao News](https://github.com/contao/news-bundle) 5.1 19 | - [Haste](https://github.com/codefog/contao-haste) 5.0 20 | 21 | Multilingual features requirements: 22 | 23 | - [DC_Multilingual](https://github.com/terminal42/contao-dc_multilingual) 4.0 24 | - [ChangeLanguage](https://github.com/terminal42/contao-changelanguage) 3.1 (recommended) 25 | 26 | 27 | ## Install using Composer 28 | 29 | Execute the following command in your Contao project folder: 30 | 31 | $ composer require codefog/contao-news_categories 32 | 33 | Then run the Contao install tool to update the database. 34 | -------------------------------------------------------------------------------- /docs/multilingual-features.md: -------------------------------------------------------------------------------- 1 | # Multilingual features – News Categories bundle 2 | 3 | 1. [Installation](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [**Multilingual features**](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | 11 | 12 | ## Requirements 13 | 14 | To enable the multilingual features make sure you have installed the necessary packages listed in the 15 | [Installation](installation.md) chapter. 16 | 17 | 18 | ## Category language management 19 | 20 | You can manage the multilingual records of categories inside the category edit form. The language switch bar is located 21 | at the top of the form: 22 | 23 | ![](images/category-language.png) 24 | 25 | 26 | ## Translate category URL parameter 27 | 28 | If you have the website in other language than English or have multiple languages on your installation, you can set 29 | the custom category URL parameter per each website root: 30 | 31 | ![](images/page-settings.png) 32 | 33 | Take a look at the example table below: 34 | 35 | Website root | Field value | Result URL 36 | --- | --- | --- 37 | English | empty (default to "category") | /en/news/category/music.html 38 | German | kategorie | /de/news/kategorie/music.html 39 | Polish | kategoria | /pl/news/kategoria/music.html 40 | -------------------------------------------------------------------------------- /docs/news-feeds.md: -------------------------------------------------------------------------------- 1 | # News feeds – News Categories bundle 2 | 3 | 1. [Installation](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [Template adjustments](template-adjustments.md) 7 | 5. [**News feeds**](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | 11 | 12 | ## Filter and display categories 13 | 14 | Each feed can be filtered by the categories. This feature can be configured in the feed settings. 15 | 16 | Additionally, you can display the categories of each article in the title or the teaser: 17 | 18 | ![](images/feeds.png) 19 | -------------------------------------------------------------------------------- /docs/template-adjustments.md: -------------------------------------------------------------------------------- 1 | # Template adjustments – News Categories bundle 2 | 3 | 1. [Installation](installation.md) 4 | 2. [Configuration](configuration.md) 5 | 3. [Frontend modules](frontend-modules.md) 6 | 4. [**Template adjustments**](template-adjustments.md) 7 | 5. [News feeds](news-feeds.md) 8 | 6. [Multilingual features](multilingual-features.md) 9 | 7. [Insert tags](insert-tags.md) 10 | 11 | 12 | ## Display categories in news templates 13 | 14 | You can display the categories in your custom `news_` templates. See the different possibilities below. 15 | 16 | ### Full category list 17 | 18 | All the categories data is added to every news partial template which allows you to render any HTML markup 19 | of the categories you can imagine. The categories are available in the `$this->categories` variable. 20 | 21 | **Pro tip:** access the news category model directly via `$category['model']`. 22 | 23 | Example: 24 | 25 | ```php 26 | categories): ?> 27 | 44 | 45 | ``` 46 | 47 | ### Simple category list 48 | 49 | To display a simple, plain-text category list you can use the array of `$this->categoriesList`: 50 | 51 | ```php 52 |

Categories: categoriesList) ?>

53 | ``` 54 | 55 | Example output markup: 56 | 57 | ```html 58 |

Categories: Music, Sport

59 | ``` 60 | 61 | 62 | ## Categories list templates 63 | 64 | The categories list module is similar to the navigation module. It also uses the two templates to render the markup: 65 | 66 | 1. `mod_newscategories` – the "wrapper" for the category navigation tree 67 | 2. `nav_newscategories` – the partial template used to generate the navigation items recursively 68 | 69 | Both of the templates can be overwritten in the frontend module settings. 70 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | ruleWithConfiguration(HeaderCommentFixer::class, [ 11 | 'header' => << 16 | @license MIT 17 | EOF 18 | , 19 | ]); 20 | }; 21 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - '#no value type specified in iterable type array#' 4 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefog/contao-news_categories/1a1418d59d55ad09b26a9d41aefd710b7845f9cd/public/icon.png -------------------------------------------------------------------------------- /src/CodefogNewsCategoriesBundle.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle; 14 | 15 | use Symfony\Component\HttpKernel\Bundle\Bundle; 16 | 17 | class CodefogNewsCategoriesBundle extends Bundle 18 | { 19 | public function getPath(): string 20 | { 21 | return \dirname(__DIR__); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ContaoManager/Plugin.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\ContaoManager; 14 | 15 | use Codefog\HasteBundle\CodefogHasteBundle; 16 | use Codefog\NewsCategoriesBundle\CodefogNewsCategoriesBundle; 17 | use Contao\CoreBundle\ContaoCoreBundle; 18 | use Contao\ManagerPlugin\Bundle\BundlePluginInterface; 19 | use Contao\ManagerPlugin\Bundle\Config\BundleConfig; 20 | use Contao\ManagerPlugin\Bundle\Parser\ParserInterface; 21 | use Contao\NewsBundle\ContaoNewsBundle; 22 | 23 | class Plugin implements BundlePluginInterface 24 | { 25 | public function getBundles(ParserInterface $parser) 26 | { 27 | return [ 28 | BundleConfig::create(CodefogNewsCategoriesBundle::class)->setLoadAfter([ 29 | ContaoCoreBundle::class, 30 | ContaoNewsBundle::class, 31 | CodefogHasteBundle::class, 32 | ])->setReplace(['news_categories']), 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ContentElement/NewsFilterElement.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\ContentElement; 14 | 15 | use Contao\ContentModule; 16 | use Contao\Controller; 17 | use Contao\ModuleModel; 18 | use Contao\StringUtil; 19 | use Contao\System; 20 | 21 | /** 22 | * @property int $news_module 23 | * @property string|array $news_filterCategories 24 | * @property bool $news_relatedCategories 25 | * @property bool $news_includeSubcategories 26 | * @property string|array $news_filterDefault 27 | * @property bool $news_filterPreserveefault 28 | * @property int $news_categoryFilterPage 29 | * @property string|array $news_categoryImgSize 30 | * @property bool $news_filterPreserve 31 | */ 32 | class NewsFilterElement extends ContentModule 33 | { 34 | /** 35 | * Parse the template. 36 | * 37 | * @return string 38 | */ 39 | public function generate() 40 | { 41 | // Return if the element is not published 42 | if ($this->isHidden()) { 43 | return ''; 44 | } 45 | 46 | // Return if the module could not be found 47 | if (null === ($moduleModel = ModuleModel::findById($this->news_module))) { 48 | return ''; 49 | } 50 | 51 | // Clone the model, so we do not modify the shared model in the registry 52 | $objModel = $moduleModel->cloneOriginal(); 53 | 54 | $this->mergeCssId($objModel); 55 | 56 | if (!empty($this->headline) && !empty($this->hl)) { 57 | $objModel->hl = $this->hl; 58 | $objModel->headline = $this->headline; 59 | } 60 | 61 | // Override news filter settings 62 | $objModel->news_filterCategories = $this->news_filterCategories; 63 | $objModel->news_relatedCategories = $this->news_relatedCategories; 64 | $objModel->news_includeSubcategories = $this->news_includeSubcategories; 65 | $objModel->news_filterDefault = $this->news_filterDefault; 66 | $objModel->news_filterPreserve = $this->news_filterPreserve; 67 | $objModel->news_categoryFilterPage = $this->news_categoryFilterPage; 68 | $objModel->news_categoryImgSize = $this->news_categoryImgSize; 69 | 70 | // Make the original content element accessible from the module template 71 | $objModel->newsFilterElement = $this; 72 | 73 | // Tag the content element (see #2137) 74 | if (null !== $this->objModel) { 75 | System::getContainer()->get('contao.cache.entity_tags')?->tagWithModelInstance($this->objModel); 76 | } 77 | 78 | return Controller::getFrontendModule($objModel, $this->strColumn); 79 | } 80 | 81 | /** 82 | * Merge the CSS/ID stuff. 83 | */ 84 | private function mergeCssId(ModuleModel $module): void 85 | { 86 | $cssID = StringUtil::deserialize($module->cssID, true); 87 | 88 | // Override the CSS ID (see #305) 89 | if (!empty($this->cssID[0])) { 90 | $cssID[0] = $this->cssID[0]; 91 | } 92 | 93 | // Merge the CSS classes (see #6011) 94 | if (!empty($this->cssID[1])) { 95 | $cssID[1] = trim(($cssID[1] ?? '').' '.$this->cssID[1]); 96 | } 97 | 98 | $module->cssID = $cssID; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Criteria/NewsCriteria.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\Criteria; 14 | 15 | use Codefog\HasteBundle\Model\DcaRelationsModel; 16 | use Codefog\NewsCategoriesBundle\Exception\NoNewsException; 17 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 18 | use Contao\CoreBundle\Framework\ContaoFramework; 19 | use Contao\CoreBundle\Security\Authentication\Token\TokenChecker; 20 | use Contao\Database; 21 | use Contao\Date; 22 | use Contao\NewsModel; 23 | 24 | class NewsCriteria 25 | { 26 | private array $columns = []; 27 | 28 | private array $values = []; 29 | 30 | private array $options = []; 31 | 32 | public function __construct( 33 | private readonly ContaoFramework $framework, 34 | private readonly TokenChecker $tokenChecker, 35 | ) { 36 | } 37 | 38 | /** 39 | * Set the basic criteria. 40 | * 41 | * @throws NoNewsException 42 | */ 43 | public function setBasicCriteria(array $archives, string|null $sorting = null, string|null $featured = null): self 44 | { 45 | $archives = $this->parseIds($archives); 46 | 47 | if (0 === \count($archives)) { 48 | throw new NoNewsException(); 49 | } 50 | 51 | $t = NewsModel::getTable(); 52 | 53 | $this->columns[] = "$t.pid IN(".implode(',', array_map('intval', $archives)).')'; 54 | 55 | $order = ''; 56 | 57 | if ('featured_first' === $featured) { 58 | $order .= "$t.featured DESC, "; 59 | } 60 | 61 | match ($sorting) { 62 | 'order_headline_asc' => $order .= "$t.headline", 63 | 'order_headline_desc' => $order .= "$t.headline DESC", 64 | 'order_random' => $order .= 'RAND()', 65 | 'order_date_asc' => $order .= "$t.date", 66 | default => $order .= "$t.date DESC", 67 | }; 68 | 69 | $this->options['order'] = $order; 70 | 71 | // Never return unpublished elements in the back end, so they don't end up in the RSS feed 72 | if (!$this->tokenChecker->isPreviewMode()) { 73 | $time = Date::floorToMinute(); 74 | $this->columns[] = "$t.published=? AND ($t.start=? OR $t.start<=?) AND ($t.stop=? OR $t.stop>?)"; 75 | $this->values = array_merge($this->values, [1, '', $time, '', $time]); 76 | } 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Set the features items. 83 | * 84 | * @param bool $enable 85 | */ 86 | public function setFeatured($enable): self 87 | { 88 | $t = NewsModel::getTable(); 89 | 90 | if (true === $enable) { 91 | $this->columns[] = "$t.featured=?"; 92 | $this->values[] = 1; 93 | } elseif (false === $enable) { 94 | $this->columns[] = "$t.featured=?"; 95 | $this->values[] = ''; 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Set the time frame. 103 | * 104 | * @param int $begin 105 | * @param int $end 106 | */ 107 | public function setTimeFrame($begin, $end): self 108 | { 109 | $t = NewsModel::getTable(); 110 | 111 | $this->columns[] = "$t.date>=? AND $t.date<=?"; 112 | $this->values[] = $begin; 113 | $this->values[] = $end; 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * Set the default categories. 120 | * 121 | * @param bool $includeSubcategories 122 | * 123 | * @throws NoNewsException 124 | */ 125 | public function setDefaultCategories(array $defaultCategories, $includeSubcategories = true, string|null $order = null): self 126 | { 127 | $defaultCategories = $this->parseIds($defaultCategories); 128 | 129 | if (0 === \count($defaultCategories)) { 130 | throw new NoNewsException(); 131 | } 132 | 133 | // Include the subcategories 134 | if ($includeSubcategories) { 135 | $newsCategoryModel = $this->framework->getAdapter(NewsCategoryModel::class); 136 | $defaultCategories = $newsCategoryModel->getAllSubcategoriesIds($defaultCategories); 137 | } 138 | 139 | /** @var DcaRelationsModel $model */ 140 | $model = $this->framework->getAdapter(DcaRelationsModel::class); 141 | 142 | $newsIds = $model->getReferenceValues('tl_news', 'categories', $defaultCategories); 143 | $newsIds = $this->parseIds($newsIds); 144 | 145 | if (0 === \count($newsIds)) { 146 | throw new NoNewsException(); 147 | } 148 | 149 | $t = NewsModel::getTable(); 150 | 151 | $this->columns['defaultCategories'] = "$t.id IN(".implode(',', $newsIds).')'; 152 | 153 | // Order news items by best match 154 | if ('best_match' === $order) { 155 | $mapper = []; 156 | 157 | // Build the mapper 158 | foreach (array_unique($newsIds) as $newsId) { 159 | $mapper[$newsId] = \count(array_intersect($defaultCategories, array_unique($model->getRelatedValues($t, 'categories', $newsId)))); 160 | } 161 | 162 | arsort($mapper); 163 | 164 | $this->options['order'] = Database::getInstance()->findInSet("$t.id", array_keys($mapper)); 165 | } 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Set the category (intersection filtering). 172 | * 173 | * @param int $category 174 | * @param bool $preserveDefault 175 | * @param bool $includeSubcategories 176 | * 177 | * @throws NoNewsException 178 | */ 179 | public function setCategory($category, $preserveDefault = false, $includeSubcategories = false): self 180 | { 181 | /** @var DcaRelationsModel $model */ 182 | $model = $this->framework->getAdapter(DcaRelationsModel::class); 183 | 184 | // Include the subcategories 185 | if ($includeSubcategories) { 186 | $newsCategoryModel = $this->framework->getAdapter(NewsCategoryModel::class); 187 | $category = $newsCategoryModel->getAllSubcategoriesIds($category); 188 | } 189 | 190 | $newsIds = $model->getReferenceValues('tl_news', 'categories', $category); 191 | $newsIds = $this->parseIds($newsIds); 192 | 193 | if (0 === \count($newsIds)) { 194 | throw new NoNewsException(); 195 | } 196 | 197 | // Do not preserve the default categories 198 | if (!$preserveDefault) { 199 | unset($this->columns['defaultCategories']); 200 | } 201 | 202 | $t = NewsModel::getTable(); 203 | 204 | $this->columns[] = "$t.id IN(".implode(',', $newsIds).')'; 205 | 206 | return $this; 207 | } 208 | 209 | /** 210 | * Set the categories (union filtering). 211 | * 212 | * @param array $categories 213 | * @param bool $preserveDefault 214 | * @param bool $includeSubcategories 215 | * 216 | * @throws NoNewsException 217 | */ 218 | public function setCategories($categories, $preserveDefault = false, $includeSubcategories = false): self 219 | { 220 | $allNewsIds = []; 221 | 222 | $model = $this->framework->getAdapter(DcaRelationsModel::class); 223 | 224 | foreach ($categories as $category) { 225 | // Include the subcategories 226 | if ($includeSubcategories) { 227 | $newsCategoryModel = $this->framework->getAdapter(NewsCategoryModel::class); 228 | $category = $newsCategoryModel->getAllSubcategoriesIds($category); 229 | } 230 | 231 | $newsIds = $model->getReferenceValues('tl_news', 'categories', $category); 232 | $newsIds = $this->parseIds($newsIds); 233 | 234 | if (0 === \count($newsIds)) { 235 | continue; 236 | } 237 | 238 | $allNewsIds = array_merge($allNewsIds, $newsIds); 239 | } 240 | 241 | if (0 === \count($allNewsIds)) { 242 | throw new NoNewsException(); 243 | } 244 | 245 | // Do not preserve the default categories 246 | if (!$preserveDefault) { 247 | unset($this->columns['defaultCategories']); 248 | } 249 | 250 | $t = NewsModel::getTable(); 251 | 252 | $this->columns[] = "$t.id IN(".implode(',', $allNewsIds).')'; 253 | 254 | return $this; 255 | } 256 | 257 | /** 258 | * Set the excluded news IDs. 259 | */ 260 | public function setExcludedNews(array $newsIds): self 261 | { 262 | $newsIds = $this->parseIds($newsIds); 263 | 264 | if ([] === $newsIds) { 265 | throw new NoNewsException(); 266 | } 267 | 268 | $t = NewsModel::getTable(); 269 | 270 | $this->columns[] = "$t.id NOT IN (".implode(',', $newsIds).')'; 271 | 272 | return $this; 273 | } 274 | 275 | /** 276 | * Set the limit. 277 | */ 278 | public function setLimit(int|null $limit): self 279 | { 280 | if (null === $limit) { 281 | unset($this->options['limit']); 282 | } else { 283 | $this->options['limit'] = $limit; 284 | } 285 | 286 | return $this; 287 | } 288 | 289 | /** 290 | * Set the offset. 291 | */ 292 | public function setOffset(int|null $offset): self 293 | { 294 | if (null === $offset) { 295 | unset($this->options['offset']); 296 | } else { 297 | $this->options['offset'] = $offset; 298 | } 299 | 300 | return $this; 301 | } 302 | 303 | public function getColumns(): array 304 | { 305 | return $this->columns; 306 | } 307 | 308 | public function getValues(): array 309 | { 310 | return $this->values; 311 | } 312 | 313 | public function getOptions(): array 314 | { 315 | return $this->options; 316 | } 317 | 318 | /** 319 | * Parse the record IDs. 320 | * 321 | * @return array 322 | */ 323 | private function parseIds(array $ids): array 324 | { 325 | $ids = array_map('intval', $ids); 326 | $ids = array_filter($ids); 327 | $ids = array_unique($ids); 328 | 329 | return array_values($ids); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/Criteria/NewsCriteriaBuilder.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\Criteria; 14 | 15 | use Codefog\HasteBundle\Model\DcaRelationsModel; 16 | use Codefog\NewsCategoriesBundle\Exception\CategoryFilteringNotAppliedException; 17 | use Codefog\NewsCategoriesBundle\Exception\CategoryNotFoundException; 18 | use Codefog\NewsCategoriesBundle\Exception\NoNewsException; 19 | use Codefog\NewsCategoriesBundle\FrontendModule\CumulativeFilterModule; 20 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsListModule; 21 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsModule; 22 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 23 | use Codefog\NewsCategoriesBundle\NewsCategoriesManager; 24 | use Contao\CoreBundle\Framework\ContaoFramework; 25 | use Contao\CoreBundle\Security\Authentication\Token\TokenChecker; 26 | use Contao\Input; 27 | use Contao\Module; 28 | use Contao\StringUtil; 29 | use Doctrine\DBAL\Connection; 30 | use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; 31 | 32 | #[Autoconfigure(public: true)] 33 | class NewsCriteriaBuilder 34 | { 35 | public function __construct( 36 | private readonly ContaoFramework $framework, 37 | private readonly TokenChecker $tokenChecker, 38 | private readonly Connection $db, 39 | private readonly NewsCategoriesManager $manager, 40 | ) { 41 | } 42 | 43 | public function create(array $archives): NewsCriteria 44 | { 45 | $criteria = new NewsCriteria($this->framework, $this->tokenChecker); 46 | $criteria->setBasicCriteria($archives); 47 | 48 | return $criteria; 49 | } 50 | 51 | /** 52 | * Get the criteria for archive module. 53 | * 54 | * @param NewsModule $module 55 | */ 56 | public function getCriteriaForArchiveModule(array $archives, int $begin, int $end, Module $module): NewsCriteria|null 57 | { 58 | $criteria = new NewsCriteria($this->framework, $this->tokenChecker); 59 | 60 | try { 61 | $criteria->setBasicCriteria($archives, $module->news_order); 62 | 63 | // Set the time frame 64 | $criteria->setTimeFrame($begin, $end); 65 | 66 | // Set the regular list criteria 67 | $this->setRegularListCriteria($criteria, $module); 68 | } catch (NoNewsException) { 69 | return null; 70 | } catch (CategoryFilteringNotAppliedException $e) { 71 | // noop 72 | } 73 | 74 | return $criteria; 75 | } 76 | 77 | /** 78 | * Get the criteria for list module. 79 | */ 80 | public function getCriteriaForListModule(array $archives, bool|null $featured, Module $module, bool $throwOnFilteringNotApplied = false): NewsCriteria|null 81 | { 82 | $criteria = new NewsCriteria($this->framework, $this->tokenChecker); 83 | 84 | try { 85 | $criteria->setBasicCriteria($archives, $module->news_order, $module->news_featured); 86 | 87 | // Set the featured filter 88 | if (null !== $featured) { 89 | $criteria->setFeatured($featured); 90 | } 91 | 92 | // Set the criteria for related categories 93 | if ($module instanceof NewsListModule && $module->news_relatedCategories) { 94 | $this->setRelatedListCriteria($criteria, $module); 95 | } else { 96 | // Set the regular list criteria 97 | $this->setRegularListCriteria($criteria, $module, $throwOnFilteringNotApplied); 98 | } 99 | } catch (NoNewsException) { 100 | return null; 101 | } catch (CategoryFilteringNotAppliedException $e) { 102 | if ($throwOnFilteringNotApplied) { 103 | throw $e; 104 | } 105 | } 106 | 107 | return $criteria; 108 | } 109 | 110 | /** 111 | * Get the criteria for menu module. 112 | * 113 | * @param NewsModule $module 114 | */ 115 | public function getCriteriaForMenuModule(array $archives, Module $module): NewsCriteria|null 116 | { 117 | $criteria = new NewsCriteria($this->framework, $this->tokenChecker); 118 | 119 | try { 120 | $criteria->setBasicCriteria($archives, $module->news_order); 121 | 122 | // Set the regular list criteria 123 | $this->setRegularListCriteria($criteria, $module); 124 | } catch (NoNewsException) { 125 | return null; 126 | } catch (CategoryFilteringNotAppliedException $e) { 127 | // noop 128 | } 129 | 130 | return $criteria; 131 | } 132 | 133 | /** 134 | * Set the regular list criteria. 135 | * 136 | * @throws CategoryNotFoundException 137 | * @throws NoNewsException 138 | */ 139 | private function setRegularListCriteria(NewsCriteria $criteria, Module $module): void 140 | { 141 | $filteringApplied = false; 142 | 143 | // Filter by default categories 144 | if (!empty($default = StringUtil::deserialize($module->news_filterDefault, true))) { 145 | $criteria->setDefaultCategories($default); 146 | $filteringApplied = true; 147 | } 148 | 149 | // Filter by multiple active categories 150 | if ($module->news_filterCategoriesCumulative) { 151 | $input = $this->framework->getAdapter(Input::class); 152 | $param = $this->manager->getParameterName(); 153 | 154 | if ($aliases = $input->get($param)) { 155 | $aliases = StringUtil::trimsplit(CumulativeFilterModule::getCategorySeparator(), $aliases); 156 | $aliases = array_unique(array_filter($aliases)); 157 | 158 | if (\count($aliases) > 0) { 159 | $model = $this->framework->getAdapter(NewsCategoryModel::class); 160 | $categories = []; 161 | 162 | foreach ($aliases as $alias) { 163 | // Return null if the category does not exist 164 | if (null === ($category = $model->findPublishedByIdOrAlias($alias))) { 165 | throw new CategoryNotFoundException(\sprintf('News category "%s" was not found', $alias)); 166 | } 167 | 168 | $categories[] = (int) $category->id; 169 | } 170 | 171 | if ($module->news_filterCategoriesUnion) { 172 | $criteria->setCategories($categories, (bool) $module->news_filterPreserve, (bool) $module->news_includeSubcategories); 173 | } else { 174 | // Intersection filtering 175 | foreach ($categories as $category) { 176 | $criteria->setCategory($category, (bool) $module->news_filterPreserve, (bool) $module->news_includeSubcategories); 177 | } 178 | } 179 | } 180 | 181 | $filteringApplied = true; 182 | } 183 | } 184 | // Filter by active category 185 | elseif ($module->news_filterCategories) { 186 | /** @var Input $input */ 187 | $input = $this->framework->getAdapter(Input::class); 188 | $param = $this->manager->getParameterName(); 189 | 190 | if ($alias = $input->get($param)) { 191 | $model = $this->framework->getAdapter(NewsCategoryModel::class); 192 | 193 | // Return null if the category does not exist 194 | if (null === ($category = $model->findPublishedByIdOrAlias($alias))) { 195 | throw new CategoryNotFoundException(\sprintf('News category "%s" was not found', $alias)); 196 | } 197 | 198 | $criteria->setCategory($category->id, (bool) $module->news_filterPreserve, (bool) $module->news_includeSubcategories); 199 | 200 | $filteringApplied = true; 201 | } 202 | } 203 | 204 | if (!$filteringApplied) { 205 | throw new CategoryFilteringNotAppliedException(); 206 | } 207 | } 208 | 209 | /** 210 | * Set the related list criteria. 211 | * 212 | * @throws NoNewsException 213 | */ 214 | private function setRelatedListCriteria(NewsCriteria $criteria, NewsListModule $module): void 215 | { 216 | if (null === ($news = $module->currentNews)) { 217 | throw new NoNewsException(); 218 | } 219 | 220 | $adapter = $this->framework->getAdapter(DcaRelationsModel::class); 221 | $categories = array_unique($adapter->getRelatedValues($news->getTable(), 'categories', $news->id)); 222 | 223 | // This news has no news categories assigned 224 | if (0 === \count($categories)) { 225 | throw new NoNewsException(); 226 | } 227 | 228 | $categories = array_map('intval', $categories); 229 | $excluded = $this->db->fetchFirstColumn('SELECT id FROM tl_news_category WHERE excludeInRelated=1'); 230 | 231 | // Exclude the categories 232 | foreach ($excluded as $category) { 233 | if (false !== ($index = array_search((int) $category, $categories, true))) { 234 | unset($categories[$index]); 235 | } 236 | } 237 | 238 | // Exclude categories by root 239 | if ($module->news_categoriesRoot > 0) { 240 | $categories = array_intersect($categories, NewsCategoryModel::getAllSubcategoriesIds($module->news_categoriesRoot)); 241 | } 242 | 243 | // There are no categories left 244 | if (0 === \count($categories)) { 245 | throw new NoNewsException(); 246 | } 247 | 248 | $criteria->setDefaultCategories($categories, (bool) $module->news_includeSubcategories, $module->news_relatedCategoriesOrder); 249 | $criteria->setExcludedNews([$news->id]); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/DependencyInjection/CodefogNewsCategoriesExtension.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\DependencyInjection; 14 | 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\DependencyInjection\Extension\Extension; 18 | use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; 19 | 20 | class CodefogNewsCategoriesExtension extends Extension 21 | { 22 | public function load(array $configs, ContainerBuilder $container): void 23 | { 24 | $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../config')); 25 | $loader->load('services.yml'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/EventListener/ChangeLanguageListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Codefog\NewsCategoriesBundle\NewsCategoriesManager; 17 | use Contao\CoreBundle\DependencyInjection\Attribute\AsHook; 18 | use Contao\CoreBundle\Framework\ContaoFramework; 19 | use Terminal42\ChangeLanguage\Event\ChangelanguageNavigationEvent; 20 | use Terminal42\DcMultilingualBundle\Model\Multilingual; 21 | 22 | #[AsHook('changelanguageNavigation')] 23 | class ChangeLanguageListener 24 | { 25 | public function __construct( 26 | private readonly ContaoFramework $framework, 27 | private readonly NewsCategoriesManager $manager, 28 | ) { 29 | } 30 | 31 | public function __invoke(ChangelanguageNavigationEvent $event): void 32 | { 33 | $this->updateAlias($event); 34 | $this->updateParameter($event); 35 | } 36 | 37 | /** 38 | * Update the category alias value. 39 | */ 40 | private function updateAlias(ChangelanguageNavigationEvent $event): void 41 | { 42 | $modelAdapter = $this->framework->getAdapter(NewsCategoryModel::class); 43 | $param = $this->manager->getParameterName(); 44 | 45 | if (!($alias = $event->getUrlParameterBag()->getUrlAttribute($param))) { 46 | return; 47 | } 48 | 49 | $model = $modelAdapter->findPublishedByIdOrAlias($alias); 50 | 51 | // Set the alias only for multilingual models 52 | if (null !== $model && $model instanceof Multilingual) { 53 | $event->getUrlParameterBag()->setUrlAttribute( 54 | $param, 55 | $model->getAlias($event->getNavigationItem()->getRootPage()->rootLanguage), 56 | ); 57 | } 58 | } 59 | 60 | /** 61 | * Update the parameter name. 62 | */ 63 | private function updateParameter(ChangelanguageNavigationEvent $event): void 64 | { 65 | $currentParam = $this->manager->getParameterName(); 66 | $newParam = $this->manager->getParameterName($event->getNavigationItem()->getRootPage()->id); 67 | 68 | $parameters = $event->getUrlParameterBag(); 69 | $attributes = $parameters->getUrlAttributes(); 70 | 71 | if (!isset($attributes[$currentParam])) { 72 | return; 73 | } 74 | 75 | // Only add or change category param if the fallback page is a direct fallback 76 | if ($event->getNavigationItem()->isDirectFallback()) { 77 | $attributes[$newParam] = $attributes[$currentParam]; 78 | 79 | if ($newParam !== $currentParam) { 80 | unset($attributes[$currentParam]); 81 | } 82 | } else { 83 | // Remove category param completely 84 | unset($attributes[$currentParam]); 85 | } 86 | 87 | $parameters->setUrlAttributes($attributes); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/CategoryPermissionListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Contao\BackendUser; 16 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 17 | use Contao\DataContainer; 18 | use Contao\StringUtil; 19 | use Doctrine\DBAL\Connection; 20 | use Symfony\Bundle\SecurityBundle\Security; 21 | use Symfony\Component\HttpFoundation\RequestStack; 22 | use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; 23 | 24 | #[AsCallback('tl_news_category', 'config.onsubmit')] 25 | class CategoryPermissionListener 26 | { 27 | public function __construct( 28 | private readonly RequestStack $requestStack, 29 | private readonly Security $security, 30 | private readonly Connection $connection, 31 | ) { 32 | } 33 | 34 | public function __invoke(DataContainer $dc): void 35 | { 36 | if ($this->security->isGranted('ROLE_ADMIN')) { 37 | return; 38 | } 39 | 40 | $categoryId = (int) $dc->id; 41 | $user = $this->getUser(); 42 | 43 | /** @var AttributeBag $bag */ 44 | $bag = $this->requestStack->getSession()->getBag('contao_backend'); 45 | $newRecords = $bag->get('new_records', [])['tl_news_category'] ?? []; 46 | $newRecords = array_map('intval', $newRecords); 47 | 48 | if ( 49 | !\in_array($categoryId, $newRecords, true) 50 | || \in_array($categoryId, array_map('intval', $user->newscategories_roots), true) 51 | ) { 52 | return; 53 | } 54 | 55 | // Add the permissions on group level 56 | if ('custom' !== $user->inherit) { 57 | $groups = $this->connection->fetchAllAssociative( 58 | 'SELECT id, newscategories, newscategories_roots FROM tl_user_group WHERE id IN('.implode(',', array_map('intval', $user->groups)).')', 59 | ); 60 | 61 | foreach ($groups as $group) { 62 | $permissions = StringUtil::deserialize($group['newscategories'], true); 63 | 64 | if (\in_array('manage', $permissions, true)) { 65 | /** @var array $categoryIds */ 66 | $categoryIds = StringUtil::deserialize($group['newscategories_roots'], true); 67 | $categoryIds[] = $categoryId; 68 | 69 | $this->connection->update('tl_user_group', ['newscategories_roots' => serialize($categoryIds)], ['id' => $group['id']]); 70 | } 71 | } 72 | } 73 | 74 | // Add the permissions on user level 75 | if ('group' !== $user->inherit) { 76 | $permissions = StringUtil::deserialize($user->newscategories, true); 77 | 78 | if (\in_array('manage', $permissions, true)) { 79 | /** @var array $categoryIds */ 80 | $categoryIds = StringUtil::deserialize($user->newscategories_roots, true); 81 | $categoryIds[] = $categoryId; 82 | 83 | $this->connection->update('tl_user', ['newscategories_roots' => serialize($categoryIds)], ['id' => $user->id]); 84 | } 85 | } 86 | 87 | // Add the new element to the user object 88 | $user->newscategories_roots[] = $categoryId; 89 | } 90 | 91 | private function getUser(): BackendUser 92 | { 93 | $user = $this->security->getUser(); 94 | 95 | if (!$user instanceof BackendUser) { 96 | throw new \RuntimeException('The token does not contain a back end user object'); 97 | } 98 | 99 | return $user; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/CategoryRootListener.php: -------------------------------------------------------------------------------- 1 | currentPid) { 28 | return $attributes; 29 | } 30 | 31 | $rootNodes = $this->getRootNodesForNewsArchiveId($dc->currentPid); 32 | 33 | if (null !== $rootNodes) { 34 | $attributes['rootNodes'] = $rootNodes; 35 | } 36 | 37 | return $attributes; 38 | } 39 | 40 | public function reset(): void 41 | { 42 | $this->rootNodesCache = []; 43 | } 44 | 45 | private function getRootNodesForNewsArchiveId(int $id): array|null 46 | { 47 | if (!isset($this->rootNodesCache[$id])) { 48 | $archive = $this->connection->fetchAssociative('SELECT limitCategories, categories FROM tl_news_archive WHERE id=?', [$id]); 49 | 50 | if (!$archive['limitCategories']) { 51 | $this->rootNodesCache[$id] = false; 52 | } else { 53 | $this->rootNodesCache[$id] = StringUtil::deserialize($archive['categories']); 54 | } 55 | } 56 | 57 | return false === $this->rootNodesCache[$id] ? null : $this->rootNodesCache[$id]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/ContentNewsModuleOptionsListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 16 | use Doctrine\DBAL\Connection; 17 | 18 | /** 19 | * Get news modules and return them as array. 20 | */ 21 | #[AsCallback('tl_content', 'fields.news_module.options')] 22 | class ContentNewsModuleOptionsListener 23 | { 24 | public function __construct(private readonly Connection $db) 25 | { 26 | } 27 | 28 | public function __invoke(): array 29 | { 30 | $modules = []; 31 | $records = $this->db->fetchAllAssociative("SELECT m.id, m.name, t.name AS theme FROM tl_module m LEFT JOIN tl_theme t ON m.pid=t.id WHERE m.type IN ('newslist', 'newsarchive') ORDER BY t.name, m.name"); 32 | 33 | foreach ($records as $record) { 34 | $modules[$record['theme']][$record['id']] = \sprintf('%s (ID %s)', $record['name'], $record['id']); 35 | } 36 | 37 | return $modules; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/NewsArchiveOperationListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Codefog\NewsCategoriesBundle\Security\NewsCategoriesPermissions; 16 | use Contao\ArrayUtil; 17 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 18 | use Symfony\Bundle\SecurityBundle\Security; 19 | 20 | #[AsCallback('tl_news_archive', 'config.onload')] 21 | class NewsArchiveOperationListener 22 | { 23 | public function __construct(private readonly Security $security) 24 | { 25 | } 26 | 27 | public function __invoke(): void 28 | { 29 | if (!$this->security->isGranted(NewsCategoriesPermissions::USER_CAN_MANAGE_CATEGORIES)) { 30 | return; 31 | } 32 | 33 | ArrayUtil::arrayInsert( 34 | $GLOBALS['TL_DCA']['tl_news_archive']['list']['global_operations'], 1, [ 35 | 'categories' => [ 36 | 'label' => &$GLOBALS['TL_LANG']['tl_news_archive']['categories'], 37 | 'href' => 'table=tl_news_category', 38 | 'icon' => 'bundles/codefognewscategories/icon.png', 39 | 'attributes' => 'onclick="Backend.getScrollOffset()"', 40 | ], 41 | ], 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/NewsCategoriesOptionsListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 16 | use Contao\CoreBundle\Framework\ContaoFramework; 17 | use Contao\Input; 18 | use Doctrine\DBAL\Connection; 19 | 20 | #[AsCallback('tl_news', 'fields.categories.options')] 21 | #[AsCallback('tl_news_archive', 'fields.categories.options')] 22 | class NewsCategoriesOptionsListener 23 | { 24 | public function __construct( 25 | private readonly ContaoFramework $framework, 26 | private readonly Connection $connection, 27 | ) { 28 | } 29 | 30 | public function __invoke(): array 31 | { 32 | $input = $this->framework->getAdapter(Input::class); 33 | 34 | // Do not generate the options for other views than listings 35 | if ($input->get('act') && 'select' !== $input->get('act')) { 36 | return []; 37 | } 38 | 39 | return $this->generateOptionsRecursively(); 40 | } 41 | 42 | private function generateOptionsRecursively(int $pid = 0, string $prefix = ''): array 43 | { 44 | $options = []; 45 | $records = $this->connection->fetchAllAssociative('SELECT * FROM tl_news_category WHERE pid=? ORDER BY sorting', [$pid]); 46 | 47 | foreach ($records as $record) { 48 | $options[$record['id']] = $prefix.$record['title']; 49 | 50 | foreach ($this->generateOptionsRecursively((int) $record['id'], $record['title'].' / ') as $k => $v) { 51 | $options[$k] = $v; 52 | } 53 | } 54 | 55 | return $options; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/NewsCategoryAliasListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Contao\Config; 16 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 17 | use Contao\CoreBundle\Slug\Slug; 18 | use Contao\DataContainer; 19 | use Doctrine\DBAL\Connection; 20 | use Terminal42\DcMultilingualBundle\Driver; 21 | 22 | class NewsCategoryAliasListener 23 | { 24 | public function __construct( 25 | private readonly Connection $db, 26 | private readonly Slug $slug, 27 | ) { 28 | } 29 | 30 | #[AsCallback('tl_news_category', 'fields.alias.save')] 31 | public function validateAlias(string $value, DataContainer $dc): string 32 | { 33 | if ('' !== $value && $this->aliasExists($value, $dc)) { 34 | throw new \RuntimeException(\sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $value)); 35 | } 36 | 37 | return $value; 38 | } 39 | 40 | #[AsCallback('tl_news_category', 'config.onbeforesubmit')] 41 | public function generateAlias(array $values, DataContainer $dc): array 42 | { 43 | $currentRecord = $this->getCurrentRecord($dc); 44 | $title = $values['frontendTitle'] ?? $currentRecord['frontendTitle'] ?: ($values['title'] ?? $currentRecord['title']); 45 | 46 | if ('' !== ($values['alias'] ?? $currentRecord['alias'] ?? '')) { 47 | return $values; 48 | } 49 | 50 | $slugOptions = []; 51 | 52 | if (!empty($validChars = Config::get('news_categorySlugSetting'))) { 53 | $slugOptions['validChars'] = $validChars; 54 | } 55 | 56 | if ($dc instanceof Driver) { 57 | $slugOptions['locale'] = $dc->getCurrentLanguage(); 58 | } 59 | 60 | $value = $this->slug->generate($title, $slugOptions); 61 | 62 | if ($this->aliasExists($value, $dc)) { 63 | $value .= '-'.$dc->id; 64 | } 65 | 66 | $values['alias'] = $value; 67 | 68 | return $values; 69 | } 70 | 71 | private function aliasExists(string $value, DataContainer $dc): bool 72 | { 73 | $query = "SELECT id FROM {$dc->table} WHERE alias=?"; 74 | $params = [$value]; 75 | 76 | if ($dc instanceof Driver) { 77 | if ('' !== $dc->getCurrentLanguage()) { 78 | $query .= " AND {$dc->getPidColumn()}!=? AND {$dc->getLanguageColumn()}=?"; 79 | } else { 80 | $query .= " AND id!=? AND {$dc->getLanguageColumn()}=?"; 81 | } 82 | 83 | $params[] = $dc->id; 84 | $params[] = $dc->getCurrentLanguage(); 85 | } else { 86 | $query .= ' AND id!=?'; 87 | $params[] = $dc->id; 88 | } 89 | 90 | return false !== $this->db->fetchOne($query, $params); 91 | } 92 | 93 | private function getCurrentRecord(DataContainer $dc): array 94 | { 95 | if ($dc instanceof Driver && '' !== $dc->getCurrentLanguage()) { 96 | return $this->db->fetchAssociative( 97 | "SELECT * FROM {$dc->table} WHERE {$dc->getPidColumn()}=? AND {$dc->getLanguageColumn()}=?", 98 | [$dc->id, $dc->getCurrentLanguage()], 99 | ); 100 | } 101 | 102 | return $dc->getCurrentRecord(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/NewsDefaultCategoriesListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Codefog\HasteBundle\DcaRelationsManager; 16 | use Codefog\NewsCategoriesBundle\Security\NewsCategoriesPermissions; 17 | use Contao\BackendUser; 18 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 19 | use Contao\DataContainer; 20 | use Symfony\Bundle\SecurityBundle\Security; 21 | 22 | /** 23 | * Adds the default user categories for new records. 24 | */ 25 | #[AsCallback('tl_news', 'config.onsubmit')] 26 | class NewsDefaultCategoriesListener 27 | { 28 | public function __construct( 29 | private readonly DcaRelationsManager $dcaRelationsManager, 30 | private readonly Security $security, 31 | ) { 32 | } 33 | 34 | public function __invoke(DataContainer $dc): void 35 | { 36 | // Return if the user is allowed to assign categories or the record is not new 37 | if ($dc->getCurrentRecord()['tstamp'] > 0 || $this->security->isGranted(NewsCategoriesPermissions::USER_CAN_ASSIGN_CATEGORIES)) { 38 | return; 39 | } 40 | 41 | $user = $this->security->getUser(); 42 | 43 | if (!$user instanceof BackendUser || empty($user->newscategories_default)) { 44 | return; 45 | } 46 | 47 | $field = $dc->field; 48 | $dc->field = 'categories'; 49 | 50 | $this->dcaRelationsManager->updateRelatedRecords((array) $user->newscategories_default, $dc); 51 | 52 | // Reset back the field property 53 | $dc->field = $field; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/EventListener/DataContainer/SettingSlugOptionsListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener\DataContainer; 14 | 15 | use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; 16 | use Contao\CoreBundle\Slug\ValidCharacters; 17 | use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; 18 | 19 | #[Autoconfigure(bind: ['$validCharacters' => '@contao.slug.valid_characters'])] 20 | #[AsCallback('tl_settings', 'fields.news_categorySlugSetting.options')] 21 | class SettingSlugOptionsListener 22 | { 23 | public function __construct(private readonly ValidCharacters $validCharacters) 24 | { 25 | } 26 | 27 | public function __invoke(): array 28 | { 29 | return $this->validCharacters->getOptions(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/EventListener/InsertTagsListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Codefog\NewsCategoriesBundle\NewsCategoriesManager; 17 | use Contao\CoreBundle\DependencyInjection\Attribute\AsHook; 18 | use Contao\CoreBundle\Framework\ContaoFramework; 19 | use Contao\Input; 20 | use Contao\StringUtil; 21 | 22 | #[AsHook('replaceInsertTags')] 23 | class InsertTagsListener 24 | { 25 | public function __construct( 26 | private readonly ContaoFramework $framework, 27 | private readonly NewsCategoriesManager $manager, 28 | ) { 29 | } 30 | 31 | public function __invoke(string $tag): string|false 32 | { 33 | $chunks = StringUtil::trimsplit('::', $tag); 34 | 35 | if ('news_categories' === $chunks[0]) { 36 | $input = $this->framework->getAdapter(Input::class); 37 | 38 | if ($alias = $input->get($this->manager->getParameterName())) { 39 | $model = $this->framework->getAdapter(NewsCategoryModel::class); 40 | 41 | if (null !== ($category = $model->findPublishedByIdOrAlias($alias))) { 42 | $value = $category->{$chunks[1]}; 43 | 44 | // Convert the binary to UUID for images (#147) 45 | if ('image' === $chunks[1] && $value) { 46 | return StringUtil::binToUuid($value); 47 | } 48 | 49 | return $value; 50 | } 51 | } 52 | 53 | return ''; 54 | } 55 | 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/EventListener/NewsFeedListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Contao\NewsBundle\Event\FetchArticlesForFeedEvent; 17 | use Contao\NewsBundle\Event\TransformArticleForFeedEvent; 18 | use Contao\NewsModel; 19 | use Contao\StringUtil; 20 | use Symfony\Component\EventDispatcher\Attribute\AsEventListener; 21 | use Symfony\Contracts\Translation\TranslatorInterface; 22 | 23 | class NewsFeedListener 24 | { 25 | public function __construct(private readonly TranslatorInterface $translator) 26 | { 27 | } 28 | 29 | #[AsEventListener] 30 | public function onFetchArticlesForFeed(FetchArticlesForFeedEvent $event): void 31 | { 32 | $ids = StringUtil::deserialize($event->getPageModel()->newsCategories, true); 33 | 34 | if (empty($ids)) { 35 | return; 36 | } 37 | 38 | // Articles from the Contao news bundle 39 | $articles = $event->getArticles(); 40 | 41 | /** @var NewsModel $article */ 42 | foreach ($articles as $k => $article) { 43 | $categories = array_map( 44 | static fn (NewsCategoryModel $category) => $category->id, 45 | NewsCategoryModel::findPublishedByNews($article->id, ['return' => 'Array']), 46 | ); 47 | 48 | if (!array_intersect($ids, $categories)) { 49 | unset($articles[$k]); 50 | } 51 | } 52 | 53 | $event->setArticles(array_values($articles)); 54 | } 55 | 56 | #[AsEventListener] 57 | public function onTransformArticleForFeed(TransformArticleForFeedEvent $event): void 58 | { 59 | $page = $event->getPageModel(); 60 | $feedItem = $event->getItem(); 61 | 62 | if ( 63 | !$feedItem 64 | || !$page->newsCategories_show 65 | || null === ($categoryModels = NewsCategoryModel::findPublishedByNews($event->getArticle()->id)) 66 | ) { 67 | return; 68 | } 69 | 70 | $categories = implode(', ', array_map(static fn (NewsCategoryModel $category) => $category->getTitle(), $categoryModels)); 71 | 72 | switch ($page->newsCategories_show) { 73 | case 'title': 74 | $feedItem->setTitle(\sprintf('[%s] %s', $categories, $feedItem->getTitle())); 75 | break; 76 | 77 | case 'text_before': 78 | $feedItem->setContent(\sprintf( 79 | "%s\n

%s %s

", 80 | (string) $feedItem->getContent(), 81 | $this->translator->trans('MSC.newsCategories', [], 'contao_default'), 82 | $categories, 83 | )); 84 | break; 85 | 86 | case 'text_after': 87 | $feedItem->setContent(\sprintf( 88 | "

%s %s

\n%s", 89 | $this->translator->trans('MSC.newsCategories', [], 'contao_default'), 90 | $categories, 91 | (string) $feedItem->getContent(), 92 | )); 93 | break; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/EventListener/NewsListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener; 14 | 15 | use Codefog\NewsCategoriesBundle\Criteria\NewsCriteria; 16 | use Codefog\NewsCategoriesBundle\Criteria\NewsCriteriaBuilder; 17 | use Codefog\NewsCategoriesBundle\Exception\CategoryFilteringNotAppliedException; 18 | use Codefog\NewsCategoriesBundle\Exception\CategoryNotFoundException; 19 | use Contao\CoreBundle\DependencyInjection\Attribute\AsHook; 20 | use Contao\CoreBundle\Exception\PageNotFoundException; 21 | use Contao\Model\Collection; 22 | use Contao\ModuleNewsList; 23 | use Contao\NewsModel; 24 | 25 | class NewsListener 26 | { 27 | public function __construct(private readonly NewsCriteriaBuilder $searchBuilder) 28 | { 29 | } 30 | 31 | #[AsHook('newsListCountItems')] 32 | public function onNewsListCountItems(array $archives, bool|null $featured, ModuleNewsList $module): int|false 33 | { 34 | try { 35 | if (null === ($criteria = $this->getCriteria($archives, $featured, $module))) { 36 | return 0; 37 | } 38 | } catch (CategoryFilteringNotAppliedException $e) { 39 | return false; 40 | } 41 | 42 | return NewsModel::countBy($criteria->getColumns(), $criteria->getValues()); 43 | } 44 | 45 | /** 46 | * @return Collection|null|false 47 | */ 48 | #[AsHook('newsListFetchItems')] 49 | public function onNewsListFetchItems(array $archives, bool|null $featured, int $limit, int $offset, ModuleNewsList $module): Collection|null|false 50 | { 51 | try { 52 | if (null === ($criteria = $this->getCriteria($archives, $featured, $module))) { 53 | return null; 54 | } 55 | } catch (CategoryFilteringNotAppliedException $e) { 56 | return false; 57 | } 58 | 59 | $criteria->setLimit($limit); 60 | $criteria->setOffset($offset); 61 | 62 | return NewsModel::findBy( 63 | $criteria->getColumns(), 64 | $criteria->getValues(), 65 | $criteria->getOptions(), 66 | ); 67 | } 68 | 69 | private function getCriteria(array $archives, bool|null $featured, ModuleNewsList $module): NewsCriteria|null 70 | { 71 | try { 72 | $criteria = $this->searchBuilder->getCriteriaForListModule($archives, $featured, $module, true); 73 | } catch (CategoryNotFoundException $e) { 74 | throw new PageNotFoundException($e->getMessage(), 0, $e); 75 | } 76 | 77 | return $criteria; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/EventListener/TemplateListener.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\EventListener; 14 | 15 | use Codefog\NewsCategoriesBundle\FrontendModule\NewsModule; 16 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 17 | use Codefog\NewsCategoriesBundle\NewsCategoriesManager; 18 | use Contao\CoreBundle\DependencyInjection\Attribute\AsHook; 19 | use Contao\CoreBundle\Framework\ContaoFramework; 20 | use Contao\CoreBundle\Image\Studio\Studio; 21 | use Contao\FrontendTemplate; 22 | use Contao\Model\Collection; 23 | use Contao\Module; 24 | use Contao\PageModel; 25 | use Contao\StringUtil; 26 | 27 | #[AsHook('parseArticles')] 28 | class TemplateListener 29 | { 30 | private array $urlCache = []; 31 | 32 | public function __construct( 33 | private readonly ContaoFramework $framework, 34 | private readonly NewsCategoriesManager $manager, 35 | private readonly Studio $studio, 36 | ) { 37 | } 38 | 39 | /** 40 | * @param NewsModule $module 41 | */ 42 | public function __invoke(FrontendTemplate $template, array $data, Module $module): void 43 | { 44 | $newsCategoryModelAdapter = $this->framework->getAdapter(NewsCategoryModel::class); 45 | 46 | if (null === ($models = $newsCategoryModelAdapter->findPublishedByNews($data['id']))) { 47 | $template->categories = []; 48 | $template->categoriesList = []; 49 | 50 | return; 51 | } 52 | 53 | $this->addCategoriesToTemplate($template, $module, $models); 54 | } 55 | 56 | /** 57 | * Add categories to the template. 58 | * 59 | * @param Collection $categories 60 | */ 61 | private function addCategoriesToTemplate(FrontendTemplate $template, Module $module, Collection $categories): void 62 | { 63 | $data = []; 64 | $list = []; 65 | $cssClasses = StringUtil::trimsplit(' ', $template->class); 66 | 67 | /** @var NewsCategoryModel $category */ 68 | foreach ($categories as $category) { 69 | // Skip the categories not eligible for the current module 70 | if (!$this->manager->isVisibleForModule($category, $module)) { 71 | continue; 72 | } 73 | 74 | // Add category to data and list 75 | $data[$category->id] = $this->generateCategoryData($category, $module); 76 | $list[$category->id] = $category->getTitle(); 77 | 78 | // Add the category CSS classes to news class 79 | $cssClasses = array_merge($cssClasses, StringUtil::trimsplit(' ', $category->getCssClass())); 80 | } 81 | 82 | // Sort the categories data alphabetically 83 | uasort($data, static fn ($a, $b) => strnatcasecmp((string) $a['name'], (string) $b['name'])); 84 | 85 | // Sort the category list alphabetically 86 | asort($list); 87 | 88 | $template->categories = $data; 89 | $template->categoriesList = $list; 90 | 91 | if (\count($cssClasses = array_unique($cssClasses)) > 0) { 92 | $template->class = ' '.implode(' ', $cssClasses); 93 | } 94 | } 95 | 96 | /** 97 | * Generate the category data. 98 | * 99 | * @return array 100 | */ 101 | private function generateCategoryData(NewsCategoryModel $category, Module $module) 102 | { 103 | $data = $category->row(); 104 | 105 | $data['model'] = $category; 106 | $data['name'] = $category->getTitle(); 107 | $data['class'] = $category->getCssClass(); 108 | $data['href'] = ''; 109 | $data['hrefWithParam'] = ''; 110 | $data['targetPage'] = null; 111 | 112 | $stringUtilAdapter = $this->framework->getAdapter(StringUtil::class); 113 | $data['linkTitle'] = $stringUtilAdapter->specialchars($data['name']); 114 | 115 | $pageAdapter = $this->framework->getAdapter(PageModel::class); 116 | 117 | // Overwrite the category links with filter page set in module 118 | if ($module->news_categoryFilterPage && null !== ($targetPage = $pageAdapter->findPublishedById($module->news_categoryFilterPage))) { 119 | $data['href'] = $this->manager->generateUrl($category, $targetPage); 120 | $data['hrefWithParam'] = $data['href']; 121 | $data['targetPage'] = $targetPage; 122 | } elseif (null !== ($targetPage = $this->manager->getTargetPage($category))) { 123 | // Add the category target page and URLs 124 | $data['hrefWithParam'] = $this->manager->generateUrl($category, $targetPage); 125 | $data['targetPage'] = $targetPage; 126 | 127 | // Cache URL for better performance 128 | if (!isset($this->urlCache[$targetPage->id])) { 129 | $this->urlCache[$targetPage->id] = $targetPage->getFrontendUrl(); 130 | } 131 | 132 | $data['href'] = $this->urlCache[$targetPage->id]; 133 | } 134 | 135 | // Register a function to generate category URL manually 136 | $data['generateUrl'] = fn (PageModel $page, $absolute = false) => $this->manager->generateUrl($category, $page, $absolute); 137 | 138 | // Add the image 139 | if (null !== ($image = $this->manager->getImage($category))) { 140 | $data['image'] = $this 141 | ->studio 142 | ->createFigureBuilder() 143 | ->fromFilesModel($image) 144 | ->setSize($module->news_categoryImgSize) 145 | ->build() 146 | ->getLegacyTemplateData() 147 | ; 148 | } else { 149 | $data['image'] = null; 150 | } 151 | 152 | return $data; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Exception/CategoryFilteringNotAppliedException.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | */ 10 | 11 | namespace Codefog\NewsCategoriesBundle\Exception; 12 | 13 | class CategoryFilteringNotAppliedException extends \RuntimeException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/CategoryNotFoundException.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\Exception; 14 | 15 | class CategoryNotFoundException extends \RuntimeException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Exception/NoNewsException.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\Exception; 14 | 15 | class NoNewsException extends \RuntimeException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/FrontendModule/CumulativeFilterModule.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\FrontendModule; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag; 17 | use Contao\FrontendTemplate; 18 | use Contao\StringUtil; 19 | use Contao\System; 20 | 21 | class CumulativeFilterModule extends NewsModule 22 | { 23 | /** 24 | * Template. 25 | * 26 | * @var string 27 | */ 28 | protected $strTemplate = 'mod_newscategories_cumulative'; 29 | 30 | /** 31 | * Generate the module. 32 | */ 33 | protected function compile(): void 34 | { 35 | $rootCategoryId = (int) $this->news_categoriesRoot; 36 | 37 | // Set the custom categories either by root ID or by manual selection 38 | if ($this->news_customCategories) { 39 | $customCategories = StringUtil::deserialize($this->news_categories, true); 40 | } else { 41 | $subcategories = NewsCategoryModel::findPublishedByPid($rootCategoryId); 42 | $customCategories = null !== $subcategories ? $subcategories->fetchEach('id') : []; 43 | } 44 | 45 | // Get the subcategories of custom categories 46 | if (!empty($customCategories) && $this->news_includeSubcategories) { 47 | $customCategories = NewsCategoryModel::getAllSubcategoriesIds($customCategories); 48 | } 49 | 50 | // First, fetch the active categories 51 | $this->activeCategories = $this->getActiveCategories($customCategories); 52 | 53 | // Then, fetch the inactive categories 54 | $inactiveCategories = $this->getInactiveCategories($customCategories); 55 | 56 | $this->Template->resetUrl = null; 57 | 58 | // Generate active categories 59 | if (null !== $this->activeCategories) { 60 | $this->Template->activeCategories = $this->renderNewsCategories($rootCategoryId, $this->activeCategories->fetchEach('id'), true); 61 | 62 | // Set the canonical URL 63 | if ($this->news_enableCanonicalUrls && ($responseContext = System::getContainer()->get('contao.routing.response_context_accessor')->getResponseContext())) { 64 | if ($responseContext->has(HtmlHeadBag::class)) { 65 | /** @var HtmlHeadBag $htmlHeadBag */ 66 | $htmlHeadBag = $responseContext->get(HtmlHeadBag::class); 67 | $htmlHeadBag->setCanonicalUri($GLOBALS['objPage']->getAbsoluteUrl()); 68 | } 69 | } 70 | 71 | // Add the "reset categories" link 72 | if ($this->news_resetCategories) { 73 | $this->Template->resetUrl = $this->getTargetPage()->getFrontendUrl(); 74 | } 75 | } else { 76 | $this->Template->activeCategories = ''; 77 | } 78 | 79 | // Generate inactive categories 80 | if (null !== $inactiveCategories) { 81 | $this->Template->inactiveCategories = $this->renderNewsCategories($rootCategoryId, $inactiveCategories->fetchEach('id')); 82 | } else { 83 | $this->Template->inactiveCategories = ''; 84 | } 85 | } 86 | 87 | /** 88 | * Recursively compile the news categories and return it as HTML string. 89 | * 90 | * @param int $pid 91 | * @param bool $isActiveCategories 92 | * 93 | * @return string 94 | */ 95 | protected function renderNewsCategories($pid, array $ids, $isActiveCategories = false) 96 | { 97 | if (null === ($categories = NewsCategoryModel::findPublishedByIds($ids, $pid))) { 98 | return ''; 99 | } 100 | 101 | // Layout template fallback 102 | if (!$this->navigationTpl) { 103 | $this->navigationTpl = 'nav_newscategories'; 104 | } 105 | 106 | $template = new FrontendTemplate($this->navigationTpl); 107 | $template->type = static::class; 108 | $template->cssID = $this->cssID; 109 | $template->level = 'level_1'; 110 | $template->showQuantity = $isActiveCategories ? false : (bool) $this->news_showQuantity; 111 | $template->isActiveCategories = $isActiveCategories; 112 | 113 | $items = []; 114 | $activeAliases = []; 115 | 116 | // Collect the active category parameters 117 | if (null !== $this->activeCategories) { 118 | /** @var NewsCategoryModel $activeCategory */ 119 | foreach ($this->activeCategories as $activeCategory) { 120 | $activeAliases[] = $activeCategory->getAlias($GLOBALS['TL_LANGUAGE']); 121 | } 122 | } 123 | 124 | $targetPage = $this->getTargetPage(); 125 | $baseParam = '/'.$this->manager->getParameterName($GLOBALS['objPage']->rootId).'/'; 126 | $resetUrl = $targetPage->getFrontendUrl(); 127 | 128 | /** @var NewsCategoryModel $category */ 129 | foreach ($categories as $category) { 130 | $categoryAlias = $category->getAlias($GLOBALS['TL_LANGUAGE']); 131 | 132 | // Add/remove the category alias to the active ones 133 | if (\in_array($categoryAlias, $activeAliases, true)) { 134 | $aliases = array_diff($activeAliases, [$categoryAlias]); 135 | } else { 136 | $aliases = [...$activeAliases, $categoryAlias]; 137 | } 138 | 139 | // Generate the category URL if there are any aliases to add, otherwise use the reset URL 140 | if (\count($aliases) > 0) { 141 | $url = $targetPage->getFrontendUrl($baseParam.implode(static::getCategorySeparator(), $aliases)); 142 | } else { 143 | $url = $resetUrl; 144 | } 145 | 146 | $items[] = $this->generateItem( 147 | $url, 148 | $category->getTitle(), 149 | $category->getTitle(), 150 | $this->generateItemCssClass($category), 151 | \in_array($categoryAlias, $activeAliases, true), 152 | '', 153 | $category, 154 | ); 155 | } 156 | 157 | $template->items = $items; 158 | 159 | return $template->parse(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/FrontendModule/CumulativeHierarchicalFilterModule.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\FrontendModule; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Codefog\NewsCategoriesBundle\NewsCategoriesManager; 17 | use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag; 18 | use Contao\Database; 19 | use Contao\FrontendTemplate; 20 | use Contao\Input; 21 | use Contao\System; 22 | 23 | class CumulativeHierarchicalFilterModule extends NewsModule 24 | { 25 | /** 26 | * Template. 27 | * 28 | * @var string 29 | */ 30 | protected $strTemplate = 'mod_newscategories'; 31 | 32 | /** 33 | * Generate the module. 34 | */ 35 | protected function compile(): void 36 | { 37 | $categories = $this->getCategories(); 38 | 39 | // Return if no categories are found 40 | if (null === $categories) { 41 | $this->Template->categories = ''; 42 | 43 | return; 44 | } 45 | 46 | $container = System::getContainer(); 47 | $param = $container->get(NewsCategoriesManager::class)->getParameterName(); 48 | 49 | // Get the active category 50 | if (($alias = Input::get($param)) && null !== ($activeCategory = NewsCategoryModel::findPublishedByIdOrAlias($alias))) { 51 | $this->activeCategory = $activeCategory; 52 | 53 | // Set the canonical URL 54 | if ($this->news_enableCanonicalUrls && ($responseContext = $container->get('contao.routing.response_context_accessor')->getResponseContext())) { 55 | if ($responseContext->has(HtmlHeadBag::class)) { 56 | /** @var HtmlHeadBag $htmlHeadBag */ 57 | $htmlHeadBag = $responseContext->get(HtmlHeadBag::class); 58 | $htmlHeadBag->setCanonicalUri($GLOBALS['objPage']->getAbsoluteUrl()); 59 | } 60 | } 61 | } 62 | 63 | $ids = []; 64 | 65 | // Get the parent categories IDs 66 | /** @var NewsCategoryModel $category */ 67 | foreach ($categories as $category) { 68 | $ids = array_merge($ids, Database::getInstance()->getParentRecords($category->id, $category->getTable())); 69 | } 70 | 71 | $this->Template->categories = $this->renderNewsCategories((int) $this->news_categoriesRoot, array_unique($ids)); 72 | } 73 | 74 | /** 75 | * Recursively compile the news categories and return it as HTML string. 76 | * 77 | * @param int $pid 78 | * @param int $level 79 | * 80 | * @return string 81 | */ 82 | protected function renderNewsCategories($pid, array $ids, $level = 1) 83 | { 84 | if (null === ($categories = NewsCategoryModel::findPublishedByIds($ids, $pid))) { 85 | return ''; 86 | } 87 | 88 | // Layout template fallback 89 | if (!$this->navigationTpl) { 90 | $this->navigationTpl = 'nav_newscategories_hierarchical'; 91 | } 92 | 93 | $template = new FrontendTemplate($this->navigationTpl); 94 | $template->type = static::class; 95 | $template->cssID = $this->cssID; 96 | $template->level = 'level_'.$level; 97 | $template->showQuantity = $this->news_showQuantity; 98 | 99 | $items = []; 100 | $activeCategories = $this->getActiveCategories($ids); 101 | 102 | // Add the "reset categories" link 103 | if ($this->news_resetCategories && 1 === $level) { 104 | $items[] = $this->generateItem( 105 | $this->getTargetPage()->getFrontendUrl(), 106 | $GLOBALS['TL_LANG']['MSC']['resetCategories'][0], 107 | $GLOBALS['TL_LANG']['MSC']['resetCategories'][1], 108 | 'reset', 109 | null === $activeCategories || 0 === \count($activeCategories), 110 | ); 111 | } 112 | 113 | $activeAliases = []; 114 | 115 | // Collect the active category parameters 116 | if (null !== $activeCategories) { 117 | /** @var NewsCategoryModel $activeCategory */ 118 | foreach ($activeCategories as $activeCategory) { 119 | $activeAliases[] = $activeCategory->getAlias($GLOBALS['TL_LANGUAGE']); 120 | } 121 | } 122 | 123 | $pageUrl = $this->getTargetPage()->getFrontendUrl(\sprintf('/%s', $this->manager->getParameterName($GLOBALS['objPage']->rootId)).'/%s'); 124 | $resetUrl = $this->getTargetPage()->getFrontendUrl(); 125 | 126 | /** @var NewsCategoryModel $category */ 127 | foreach ($categories as $category) { 128 | // Generate the category individual URL or the filter-link 129 | $categoryAlias = $category->getAlias($GLOBALS['TL_LANGUAGE']); 130 | 131 | // Add/remove the category alias to the active ones 132 | if (\in_array($categoryAlias, $activeAliases, true)) { 133 | $aliases = array_diff($activeAliases, [$categoryAlias]); 134 | } else { 135 | $aliases = [...$activeAliases, $categoryAlias]; 136 | } 137 | 138 | // Get the URL 139 | if (\count($aliases) > 0) { 140 | $url = \sprintf($pageUrl, implode(static::getCategorySeparator(), $aliases)); 141 | } else { 142 | $url = $resetUrl; 143 | } 144 | 145 | ++$level; 146 | 147 | $items[] = $this->generateItem( 148 | $url, 149 | $category->getTitle(), 150 | $category->getTitle(), 151 | $this->generateItemCssClass($category), 152 | null !== $activeCategories && \in_array($category, $activeCategories->getModels(), true), 153 | !$this->showLevel || $this->showLevel >= $level ? $this->renderNewsCategories($category->id, $ids, $level) : '', 154 | $category, 155 | ); 156 | } 157 | 158 | $template->items = $items; 159 | 160 | return $template->parse(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/FrontendModule/NewsArchiveModule.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\FrontendModule; 14 | 15 | use Codefog\NewsCategoriesBundle\Criteria\NewsCriteria; 16 | use Codefog\NewsCategoriesBundle\Criteria\NewsCriteriaBuilder; 17 | use Codefog\NewsCategoriesBundle\Exception\CategoryNotFoundException; 18 | use Contao\Config; 19 | use Contao\CoreBundle\Exception\PageNotFoundException; 20 | use Contao\Date; 21 | use Contao\Environment; 22 | use Contao\Input; 23 | use Contao\Model\Collection; 24 | use Contao\ModuleNewsArchive; 25 | use Contao\NewsModel; 26 | use Contao\PageModel; 27 | use Contao\Pagination; 28 | use Contao\System; 29 | use Contao\Template; 30 | 31 | /** 32 | * @property Template $Template 33 | */ 34 | class NewsArchiveModule extends ModuleNewsArchive 35 | { 36 | protected function compile(): void 37 | { 38 | /** @var PageModel $objPage */ 39 | global $objPage; 40 | 41 | $limit = null; 42 | $offset = 0; 43 | $intBegin = 0; 44 | $intEnd = 0; 45 | 46 | $intYear = (int) Input::get('year'); 47 | $intMonth = (int) Input::get('month'); 48 | $intDay = (int) Input::get('day'); 49 | 50 | // Jump to the current period 51 | if (!isset($_GET['year']) && !isset($_GET['month']) && !isset($_GET['day']) && 'all_items' !== $this->news_jumpToCurrent) { 52 | switch ($this->news_format) { 53 | case 'news_year': 54 | $intYear = date('Y'); 55 | break; 56 | 57 | default: 58 | case 'news_month': 59 | $intMonth = date('Ym'); 60 | break; 61 | 62 | case 'news_day': 63 | $intDay = date('Ymd'); 64 | break; 65 | } 66 | } 67 | 68 | // Create the date object 69 | try { 70 | if ($intYear) { 71 | $intDate = $intYear; 72 | $objDate = new Date($intDate, 'Y'); 73 | $intBegin = $objDate->yearBegin; 74 | $intEnd = $objDate->yearEnd; 75 | $this->headline .= ' '.date('Y', $objDate->tstamp); 76 | } elseif ($intMonth) { 77 | $intDate = $intMonth; 78 | $objDate = new Date($intDate, 'Ym'); 79 | $intBegin = $objDate->monthBegin; 80 | $intEnd = $objDate->monthEnd; 81 | $this->headline .= ' '.Date::parse('F Y', $objDate->tstamp); 82 | } elseif ($intDay) { 83 | $intDate = $intDay; 84 | $objDate = new Date($intDate, 'Ymd'); 85 | $intBegin = $objDate->dayBegin; 86 | $intEnd = $objDate->dayEnd; 87 | $this->headline .= ' '.Date::parse($objPage->dateFormat, $objDate->tstamp); 88 | } elseif ('all_items' === $this->news_jumpToCurrent) { 89 | $intBegin = 0; 90 | $intEnd = time(); 91 | } 92 | } catch (\OutOfBoundsException) { 93 | throw new PageNotFoundException('Page not found: '.Environment::get('uri')); 94 | } 95 | 96 | $this->Template->articles = []; 97 | 98 | // Split the result 99 | if ($this->perPage > 0) { 100 | // Get the total number of items 101 | $intTotal = $this->countNewsItems($intBegin, $intEnd); 102 | 103 | if ($intTotal > 0) { 104 | $total = $intTotal; 105 | 106 | // Get the current page 107 | $id = 'page_a'.$this->id; 108 | $page = (int) (Input::get($id) ?? 1); 109 | 110 | // Do not index or cache the page if the page number is outside the range 111 | if ($page < 1 || $page > max(ceil($total / $this->perPage), 1)) { 112 | throw new PageNotFoundException('Page not found: '.Environment::get('uri')); 113 | } 114 | 115 | // Set limit and offset 116 | $limit = (int) $this->perPage; 117 | $offset = (max($page, 1) - 1) * $this->perPage; 118 | 119 | // Add the pagination menu 120 | $objPagination = new Pagination($total, $this->perPage, Config::get('maxPaginationLinks'), $id); 121 | $this->Template->pagination = $objPagination->generate("\n "); 122 | } 123 | } 124 | 125 | // Get the news items 126 | if (isset($limit)) { 127 | $objArticles = $this->fetchNewsItems($intBegin, $intEnd, $limit, $offset); 128 | } else { 129 | $objArticles = $this->fetchNewsItems($intBegin, $intEnd); 130 | } 131 | 132 | // Add the articles 133 | if (null !== $objArticles) { 134 | $this->Template->articles = $this->parseArticles($objArticles); 135 | } 136 | 137 | $this->Template->headline = trim($this->headline); 138 | $this->Template->back = $GLOBALS['TL_LANG']['MSC']['goBack']; 139 | $this->Template->empty = $GLOBALS['TL_LANG']['MSC']['empty']; 140 | } 141 | 142 | /** 143 | * Count the news items. 144 | */ 145 | protected function countNewsItems(int $begin, int $end): int 146 | { 147 | if (null === ($criteria = $this->getSearchCriteria($begin, $end))) { 148 | return 0; 149 | } 150 | 151 | return NewsModel::countBy($criteria->getColumns(), $criteria->getValues()); 152 | } 153 | 154 | /** 155 | * Fetch the news items. 156 | * 157 | * @return Collection|null 158 | */ 159 | protected function fetchNewsItems(int $begin, int $end, int|null $limit = null, int|null $offset = null): Collection|null 160 | { 161 | if (null === ($criteria = $this->getSearchCriteria($begin, $end))) { 162 | return null; 163 | } 164 | 165 | $criteria->setLimit($limit); 166 | $criteria->setOffset($offset); 167 | 168 | return NewsModel::findBy($criteria->getColumns(), $criteria->getValues(), $criteria->getOptions()); 169 | } 170 | 171 | /** 172 | * Get the search criteria. 173 | * 174 | * @throws PageNotFoundException 175 | */ 176 | protected function getSearchCriteria(int $begin, int $end): NewsCriteria|null 177 | { 178 | try { 179 | $criteria = System::getContainer() 180 | ->get(NewsCriteriaBuilder::class) 181 | ->getCriteriaForArchiveModule($this->news_archives, $begin, $end, $this) 182 | ; 183 | } catch (CategoryNotFoundException $e) { 184 | throw new PageNotFoundException($e->getMessage(), 0, $e); 185 | } 186 | 187 | return $criteria; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/FrontendModule/NewsCategoriesModule.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\FrontendModule; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Codefog\NewsCategoriesBundle\NewsCategoriesManager; 17 | use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag; 18 | use Contao\CoreBundle\Routing\ResponseContext\ResponseContext; 19 | use Contao\Database; 20 | use Contao\FrontendTemplate; 21 | use Contao\Input; 22 | use Contao\System; 23 | 24 | class NewsCategoriesModule extends NewsModule 25 | { 26 | /** 27 | * Template. 28 | * 29 | * @var string 30 | */ 31 | protected $strTemplate = 'mod_newscategories'; 32 | 33 | /** 34 | * Generate the module. 35 | */ 36 | protected function compile(): void 37 | { 38 | $categories = $this->getCategories(); 39 | 40 | // Return if no categories are found 41 | if (null === $categories) { 42 | $this->Template->categories = ''; 43 | 44 | return; 45 | } 46 | 47 | $container = System::getContainer(); 48 | $param = $container->get(NewsCategoriesManager::class)->getParameterName(); 49 | 50 | // Get the active category 51 | if (($alias = Input::get($param)) && null !== ($activeCategory = NewsCategoryModel::findPublishedByIdOrAlias($alias))) { 52 | $this->activeCategory = $activeCategory; 53 | 54 | // Set the canonical URL 55 | if ($this->news_enableCanonicalUrls && ($responseContext = $container->get('contao.routing.response_context_accessor')->getResponseContext())) { 56 | /** @var ResponseContext $responseContext */ 57 | if ($responseContext->has(HtmlHeadBag::class)) { 58 | /** @var HtmlHeadBag $htmlHeadBag */ 59 | $htmlHeadBag = $responseContext->get(HtmlHeadBag::class); 60 | $htmlHeadBag->setCanonicalUri($GLOBALS['objPage']->getAbsoluteUrl()); 61 | } 62 | } 63 | } 64 | 65 | $ids = []; 66 | 67 | // Get the parent categories IDs 68 | /** @var NewsCategoryModel $category */ 69 | foreach ($categories as $category) { 70 | $ids = array_merge($ids, Database::getInstance()->getParentRecords($category->id, $category->getTable())); 71 | } 72 | 73 | $this->Template->categories = $this->renderNewsCategories((int) $this->news_categoriesRoot, array_unique($ids)); 74 | } 75 | 76 | /** 77 | * Recursively compile the news categories and return it as HTML string. 78 | * 79 | * @param int $pid 80 | * @param int $level 81 | * 82 | * @return string 83 | */ 84 | protected function renderNewsCategories($pid, array $ids, $level = 1) 85 | { 86 | if (null === ($categories = NewsCategoryModel::findPublishedByIds($ids, $pid))) { 87 | return ''; 88 | } 89 | 90 | // Layout template fallback 91 | if (!$this->navigationTpl) { 92 | $this->navigationTpl = 'nav_newscategories'; 93 | } 94 | 95 | $template = new FrontendTemplate($this->navigationTpl); 96 | $template->type = static::class; 97 | $template->cssID = $this->cssID; 98 | $template->level = 'level_'.$level; 99 | $template->showQuantity = $this->news_showQuantity; 100 | 101 | $items = []; 102 | 103 | // Add the "reset categories" link 104 | if ($this->news_resetCategories && 1 === $level) { 105 | $items[] = $this->generateItem( 106 | $this->getTargetPage()->getFrontendUrl(), 107 | $GLOBALS['TL_LANG']['MSC']['resetCategories'][0], 108 | $GLOBALS['TL_LANG']['MSC']['resetCategories'][1], 109 | 'reset', 110 | 0 === \count($this->currentNewsCategories) && null === $this->activeCategory, 111 | ); 112 | } 113 | 114 | ++$level; 115 | 116 | /** @var NewsCategoryModel $category */ 117 | foreach ($categories as $category) { 118 | // Generate the category individual URL or the filter-link 119 | if ($this->news_forceCategoryUrl && null !== ($targetPage = $this->manager->getTargetPage($category))) { 120 | $url = $targetPage->getFrontendUrl(); 121 | } else { 122 | $url = $this->manager->generateUrl($category, $this->getTargetPage()); 123 | } 124 | 125 | $items[] = $this->generateItem( 126 | $url, 127 | $category->getTitle(), 128 | $category->getTitle(), 129 | $this->generateItemCssClass($category), 130 | null !== $this->activeCategory && (int) $this->activeCategory->id === (int) $category->id, 131 | !$this->showLevel || $this->showLevel >= $level ? $this->renderNewsCategories($category->id, $ids, $level) : '', 132 | $category, 133 | ); 134 | } 135 | 136 | $template->items = $items; 137 | 138 | return $template->parse(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/FrontendModule/NewsListModule.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\FrontendModule; 14 | 15 | use Contao\Input; 16 | use Contao\ModuleNewsList; 17 | use Contao\NewsModel; 18 | use Contao\StringUtil; 19 | use Contao\System; 20 | 21 | /** 22 | * @property string|array $news_relatedCategories 23 | */ 24 | class NewsListModule extends ModuleNewsList 25 | { 26 | /** 27 | * Current news for future reference in search builder. 28 | */ 29 | public NewsModel|null $currentNews = null; 30 | 31 | /** 32 | * Set the flag to filter news by categories. 33 | * 34 | * @return string 35 | */ 36 | public function generate() 37 | { 38 | $container = System::getContainer(); 39 | 40 | if ( 41 | ($request = $container->get('request_stack')->getCurrentRequest()) 42 | && $container->get('contao.routing.scope_matcher')->isBackendRequest($request) 43 | ) { 44 | return parent::generate(); 45 | } 46 | 47 | // Generate the list in related categories mode 48 | if ($this->news_relatedCategories) { 49 | return $this->generateRelated(); 50 | } 51 | 52 | return parent::generate(); 53 | } 54 | 55 | /** 56 | * Generate the list in related categories mode. 57 | * 58 | * Use the categories of the current news item. The module must be 59 | * on the same page as news reader module. 60 | * 61 | * @return string 62 | */ 63 | protected function generateRelated() 64 | { 65 | $this->news_archives = $this->sortOutProtected(StringUtil::deserialize($this->news_archives, true)); 66 | 67 | // Return if there are no archives 68 | if (0 === \count($this->news_archives)) { 69 | return ''; 70 | } 71 | 72 | $alias = Input::get('items') ?: Input::get('auto_item'); 73 | 74 | // Return if the news item was not found 75 | if (($news = NewsModel::findPublishedByParentAndIdOrAlias($alias, $this->news_archives)) === null) { 76 | return ''; 77 | } 78 | 79 | // Store the news item for further reference 80 | $this->currentNews = $news; 81 | 82 | return parent::generate(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/NewsCategoriesManager.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle; 14 | 15 | use Codefog\NewsCategoriesBundle\Model\NewsCategoryModel; 16 | use Contao\CoreBundle\Framework\ContaoFramework; 17 | use Contao\Database; 18 | use Contao\FilesModel; 19 | use Contao\Module; 20 | use Contao\ModuleNewsArchive; 21 | use Contao\ModuleNewsList; 22 | use Contao\ModuleNewsReader; 23 | use Contao\PageModel; 24 | use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; 25 | use Symfony\Contracts\Service\ResetInterface; 26 | 27 | #[Autoconfigure(public: true)] 28 | class NewsCategoriesManager implements ResetInterface 29 | { 30 | private array $urlCache = []; 31 | 32 | private array $trailCache = []; 33 | 34 | public function __construct( 35 | private readonly ContaoFramework $framework, 36 | private readonly string $projectDir, 37 | ) { 38 | } 39 | 40 | /** 41 | * Generate the category URL. 42 | */ 43 | public function generateUrl(NewsCategoryModel $category, PageModel $page, bool $absolute = false): string 44 | { 45 | $cacheKey = $page->id.'-'.$category->id.'-'.($absolute ? 'abs' : 'rel'); 46 | 47 | if (isset($this->urlCache[$cacheKey])) { 48 | return $this->urlCache[$cacheKey]; 49 | } 50 | 51 | $page->loadDetails(); 52 | $params = \sprintf('/%s/%s', $this->getParameterName($page->rootId), $category->getAlias($page->language)); 53 | $this->urlCache[$cacheKey] = $absolute ? $page->getAbsoluteUrl($params) : $page->getFrontendUrl($params); 54 | 55 | return $this->urlCache[$cacheKey]; 56 | } 57 | 58 | /** 59 | * Get the image. 60 | */ 61 | public function getImage(NewsCategoryModel $category): FilesModel|null 62 | { 63 | if (null === ($image = $category->getImage()) || !is_file($this->projectDir.'/'.$image->path)) { 64 | return null; 65 | } 66 | 67 | return $image; 68 | } 69 | 70 | /** 71 | * Get the parameter name. 72 | */ 73 | public function getParameterName(int|null $rootId = null): string 74 | { 75 | $rootId = $rootId ?: $GLOBALS['objPage']->rootId; 76 | 77 | if (!$rootId || null === ($rootPage = PageModel::findById($rootId))) { 78 | return 'category'; 79 | } 80 | 81 | return $rootPage->newsCategories_param ?: 'category'; 82 | } 83 | 84 | /** 85 | * Get the category target page. 86 | */ 87 | public function getTargetPage(NewsCategoryModel $category): PageModel|null 88 | { 89 | $pageId = $category->jumpTo; 90 | 91 | // Inherit the page from parent if there is none set 92 | if (!$pageId) { 93 | $pid = $category->pid; 94 | 95 | do { 96 | $parent = $category->findById($pid); 97 | 98 | if (null !== $parent) { 99 | $pid = $parent->pid; 100 | $pageId = $parent->jumpTo; 101 | } 102 | } while ($pid && !$pageId); 103 | } 104 | 105 | // Get the page model 106 | if ($pageId) { 107 | static $pageCache = []; 108 | 109 | if (!isset($pageCache[$pageId])) { 110 | /** @var PageModel $pageAdapter */ 111 | $pageAdapter = $this->framework->getAdapter(PageModel::class); 112 | $pageCache[$pageId] = $pageAdapter->findPublishedById($pageId); 113 | } 114 | 115 | return $pageCache[$pageId]; 116 | } 117 | 118 | return null; 119 | } 120 | 121 | /** 122 | * Get the category trail IDs. 123 | */ 124 | public function getTrailIds(NewsCategoryModel $category): array 125 | { 126 | if (!isset($this->trailCache[$category->id])) { 127 | $db = $this->framework->createInstance(Database::class); 128 | 129 | $ids = $db->getParentRecords($category->id, $category->getTable()); 130 | $ids = array_map('intval', array_unique($ids)); 131 | 132 | // Remove the current category 133 | unset($ids[array_search($category->id, $ids, true)]); 134 | 135 | $this->trailCache[$category->id] = $ids; 136 | } 137 | 138 | return $this->trailCache[$category->id]; 139 | } 140 | 141 | /** 142 | * Return true if the category is visible for module. 143 | */ 144 | public function isVisibleForModule(NewsCategoryModel $category, Module $module): bool 145 | { 146 | // List or archive module 147 | if ($category->hideInList && ($module instanceof ModuleNewsList || $module instanceof ModuleNewsArchive)) { 148 | return false; 149 | } 150 | 151 | // Reader module 152 | if ($category->hideInReader && $module instanceof ModuleNewsReader) { 153 | return false; 154 | } 155 | 156 | return true; 157 | } 158 | 159 | public function reset(): void 160 | { 161 | $this->urlCache = []; 162 | $this->trailCache = []; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Security/NewsCategoriesPermissions.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\Security; 14 | 15 | final class NewsCategoriesPermissions 16 | { 17 | public const USER_CAN_MANAGE_CATEGORIES = 'contao_user.newscategories.manage'; 18 | 19 | public const USER_CAN_ASSIGN_CATEGORIES = 'contao_user.alexf.tl_news::categories'; 20 | 21 | public const USER_CAN_ACCESS_CATEGORY = 'contao_user.newscategories_roots'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Security/Voter/BackendAccessVoter.php: -------------------------------------------------------------------------------- 1 | 10 | * @license MIT 11 | */ 12 | 13 | namespace Codefog\NewsCategoriesBundle\Security\Voter; 14 | 15 | use Codefog\NewsCategoriesBundle\Security\NewsCategoriesPermissions; 16 | use Contao\BackendUser; 17 | use Contao\CoreBundle\Framework\ContaoFramework; 18 | use Contao\Database; 19 | use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; 20 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 21 | use Symfony\Component\Security\Core\Authorization\Voter\CacheableVoterInterface; 22 | use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; 23 | use Symfony\Contracts\Service\ResetInterface; 24 | 25 | /** 26 | * This voter priority-overrides the regular Contao BackendAccessVoter 27 | * to vote on tl_user.newscategories_roots recursively. 28 | */ 29 | #[AutoconfigureTag('security.voter', ['priority' => 8])] 30 | class BackendAccessVoter implements CacheableVoterInterface, ResetInterface 31 | { 32 | /** 33 | * @var array> 34 | */ 35 | private array $childRecords = []; 36 | 37 | public function __construct(private readonly ContaoFramework $framework) 38 | { 39 | } 40 | 41 | public function supportsAttribute(string $attribute): bool 42 | { 43 | return NewsCategoriesPermissions::USER_CAN_ACCESS_CATEGORY === $attribute; 44 | } 45 | 46 | public function supportsType(string $subjectType): bool 47 | { 48 | return true; 49 | } 50 | 51 | public function vote(TokenInterface $token, mixed $subject, array $attributes): int 52 | { 53 | $user = $token->getUser(); 54 | 55 | if (!$user instanceof BackendUser) { 56 | return VoterInterface::ACCESS_DENIED; 57 | } 58 | 59 | if ($user->isAdmin || empty($user->newscategories_roots)) { 60 | return VoterInterface::ACCESS_GRANTED; 61 | } 62 | 63 | $ids = $this->getChildRecords((array) $user->newscategories_roots); 64 | 65 | return \in_array((int) $subject, $ids, true) ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_DENIED; 66 | } 67 | 68 | public function reset(): void 69 | { 70 | $this->childRecords = []; 71 | } 72 | 73 | private function getChildRecords(array $ids): array 74 | { 75 | $key = implode(',', $ids); 76 | 77 | if (!isset($this->childRecords[$key])) { 78 | $this->framework->initialize(); 79 | 80 | $this->childRecords[$key] = Database::getInstance()->getChildRecords($ids, 'tl_news_category', false, $ids); 81 | } 82 | 83 | return $this->childRecords[$key]; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Security/Voter/DataContainer/NewsCategoriesVoter.php: -------------------------------------------------------------------------------- 1 | supportsAttribute($attribute)) { 42 | continue; 43 | } 44 | 45 | $isGranted = match (true) { 46 | $subject instanceof ReadAction => $this->canRead($subject), 47 | $subject instanceof CreateAction, 48 | $subject instanceof UpdateAction, 49 | $subject instanceof DeleteAction => $this->canWrite($subject), 50 | default => false, 51 | }; 52 | 53 | return $isGranted ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_DENIED; 54 | } 55 | 56 | return VoterInterface::ACCESS_ABSTAIN; 57 | } 58 | 59 | private function canRead(ReadAction $action): bool 60 | { 61 | if ($this->isNew($action)) { 62 | return true; 63 | } 64 | 65 | return $this->security->isGranted(NewsCategoriesPermissions::USER_CAN_ACCESS_CATEGORY, $action->getCurrentId()); 66 | } 67 | 68 | private function canWrite(CreateAction|DeleteAction|UpdateAction $action): bool 69 | { 70 | if (!$this->security->isGranted(NewsCategoriesPermissions::USER_CAN_MANAGE_CATEGORIES)) { 71 | return false; 72 | } 73 | 74 | if ($this->isNew($action)) { 75 | return true; 76 | } 77 | 78 | // Check if user can access current category to delete 79 | if ($action instanceof DeleteAction) { 80 | return $this->security->isGranted(NewsCategoriesPermissions::USER_CAN_ACCESS_CATEGORY, $action->getCurrentId()); 81 | } 82 | 83 | // Check the parent ID "to-be" whether the user can write into a category 84 | if ($action->getNewPid() > 0 && !$this->security->isGranted(NewsCategoriesPermissions::USER_CAN_ACCESS_CATEGORY, $action->getNewPid())) { 85 | return false; 86 | } 87 | 88 | if ($action instanceof UpdateAction) { 89 | return $this->security->isGranted(NewsCategoriesPermissions::USER_CAN_ACCESS_CATEGORY, $action->getCurrentId()); 90 | } 91 | 92 | // Allow new records without PID so the copy operation is available 93 | return true; 94 | } 95 | 96 | private function isNew(CreateAction|DeleteAction|ReadAction|UpdateAction $action): bool 97 | { 98 | $recordId = $action instanceof CreateAction ? $action->getNewId() : $action->getCurrentId(); 99 | 100 | /** @var AttributeBag $bag */ 101 | $bag = $this->requestStack->getSession()->getBag('contao_backend'); 102 | $newRecords = $bag->get('new_records', [])['tl_news_category'] ?? []; 103 | $newRecords = array_map('intval', $newRecords); 104 | 105 | return \in_array((int) $recordId, $newRecords, true); 106 | } 107 | } 108 | --------------------------------------------------------------------------------