├── .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://packagist.org/packages/codefog/contao-news_categories)
4 | [](https://github.com/codefog/contao-news_categories/blob/master/LICENSE.txt)
5 | [](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 | 
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 | = $this->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 |
= $GLOBALS['TL_LANG']['MSC']['activeCategories'] ?>
8 | = $this->activeCategories ?>
9 |
10 |
11 |
12 | inactiveCategories): ?>
13 |
14 |
= $this->activeCategories ? $GLOBALS['TL_LANG']['MSC']['inactiveCategoriesAdd'] : $GLOBALS['TL_LANG']['MSC']['inactiveCategories'] ?>
15 | = $this->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 | = $this->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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
28 | categories as $category): ?>
29 |
30 |
31 |
32 | insert('picture_default', $category['image']['picture']) ?>
33 |
34 |
35 |
36 |
37 | = $category['name'] ?>
38 |
39 | = $category['name'] ?>
40 |
41 |
42 |
43 |
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: = implode(', ', $this->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 |
--------------------------------------------------------------------------------