diff --git a/.composer-require-checker.json b/.composer-require-checker.json
new file mode 100644
index 000000000..53cf5c017
--- /dev/null
+++ b/.composer-require-checker.json
@@ -0,0 +1,16 @@
+{
+ "symbol-whitelist": [
+ "array", "bool", "false", "int", "null", "self", "static", "parent", "string", "true", "void", "mixed",
+ "Contao\\ManagerBundle\\ContaoManagerBundle",
+ "Contao\\ManagerPlugin\\Bundle\\BundlePluginInterface",
+ "Contao\\ManagerPlugin\\Bundle\\Config\\BundleConfig",
+ "Contao\\ManagerPlugin\\Bundle\\Parser\\ParserInterface",
+ "Contao\\ManagerPlugin\\Routing\\RoutingPluginInterface",
+ "Doctrine\\Bundle\\DoctrineBundle\\DoctrineBundle",
+ "Knp\\Menu\\FactoryInterface",
+ "Knp\\Menu\\ItemInterface",
+ "Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator",
+ "Symfony\\Contracts\\Cache\\CacheInterface",
+ "tl_user_group"
+ ]
+}
diff --git a/.github/workflows/diagnostics.yml b/.github/workflows/diagnostics.yml
index 38c73a3e2..c4e707777 100644
--- a/.github/workflows/diagnostics.yml
+++ b/.github/workflows/diagnostics.yml
@@ -1,52 +1,74 @@
name: MetaModels core
-
on:
- push:
- branches-ignore:
- - '**-translation'
pull_request:
+ push:
+ branches:
jobs:
build:
runs-on: ubuntu-latest
+ name: 'PHP: ${{ matrix.php }} Contao: ${{ matrix.contao }}'
strategy:
+ fail-fast: false
matrix:
- php: [7.4]
- contao: [~4.9.0]
+ php: [ '8.1', '8.2' ]
+ contao: [ '~4.13.0' ]
+ phpcq_install: [ 'update' ]
+ output: [ '-o github-action -o default' ]
steps:
- - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Pull source
+ - name: Pull source
uses: actions/checkout@v3
- with:
- fetch-depth: 0
- # see https://github.com/shivammathur/setup-php
- - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Setup PHP.
+ - name: Setup PHP with PECL extension
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- coverage: none
- - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Cache composer cache directory
+ # setup caches
+ - name: Cache composer cache directory
uses: actions/cache@v3
env:
cache-name: composer-cache-dir
with:
path: ~/.cache/composer
- key: ${{ runner.os }}-build-${{ env.cache-name }}
+ key: ${{ runner.os }}-${{ matrix.php }}-build-${{ env.cache-name }}
- - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Cache vendor directory
+ - name: Cache vendor directory
uses: actions/cache@v3
env:
- cache-name: composer-vendor
+ cache-name: vendor
with:
path: vendor
- key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/composer.lock') }}
+ key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.contao }}-build-${{ env.cache-name }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
- ${{ runner.os }}-build-${{ env.cache-name }}-
+ ${{ runner.os }}-${{ matrix.php }}-${{ matrix.contao }}-build-${{ env.cache-name }}-
- - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Install composer dependencies
- run: composer update --prefer-dist --no-interaction --no-suggest
+ - name: Cache phpcq directory
+ uses: actions/cache@v3
+ env:
+ cache-name: phpcq
+ with:
+ path: .phpcq
+ key: ${{ runner.os }}-${{ matrix.php }}-build-${{ env.cache-name }}-${{ hashFiles('**/.phpcq.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-${{ matrix.php }}-build-${{ env.cache-name }}-
+
+ # install dependencies and tools
+ - name: Install composer dependencies
+ run: |
+ composer require contao/core-bundle ${{ matrix.contao }} --no-update
+ composer install
+ - name: Install phpcq toolchain
+ run: ./vendor/bin/phpcq ${{ matrix.phpcq_install }} -v
- - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Run tests
- run: ant -keep-going
+ # run tests
+ - name: Run tests
+ run: ./vendor/bin/phpcq run -v ${{ matrix.output }}
+
+ - name: Upload build directory to artifact
+ uses: actions/upload-artifact@v3
+ if: ${{ success() }} || ${{ failure() }}
+ with:
+ name: phpcq-builds-php-${{ matrix.php }}-${{ matrix.contao }}
+ path: .phpcq/build/
diff --git a/.gitignore b/.gitignore
index f0ec851e1..6561ee7c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@
.DS_Store
Thumbs.db
-# IDEs
+# IDEs
.buildpath
.project
.settings/
@@ -16,6 +16,5 @@ vendor/
composer.lock
# build
-build/
-build.properties
-.phpunit.result.cache
\ No newline at end of file
+.phpunit.result.cache
+/.phpcq/*
diff --git a/.phpcq.lock b/.phpcq.lock
new file mode 100644
index 000000000..d79776ed4
--- /dev/null
+++ b/.phpcq.lock
@@ -0,0 +1 @@
+{"plugins":{"phpunit":{"api-version":"1.0.0","version":"1.0.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpunit/phpunit-1.0.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0"},"tool":{"phpunit":"^6.0 || ^7.0 || ^8.0 || ^9.0"}},"checksum":{"type":"sha-512","value":"c73f15658e3ba62665f09492ec91c3a6a715760bfaa88473a987538439fff442540148e086e46a6aa18ce55a3ea2fbf76caaa581384cb84a38859fcc609ae7e4"},"tools":{"phpunit":{"version":"9.6.19","url":"https://phar.phpunit.de/phpunit-9.6.19.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*","ext-json":"*","ext-libxml":"*","ext-mbstring":"*","ext-xml":"*","ext-xmlwriter":"*"}},"checksum":{"type":"sha-256","value":"f30c21743f4fbea14ad8ab8f47673f9067117262dafa147fcb182e75fbc1c249"},"signature":"https://phar.phpunit.de/phpunit-9.6.19.phar.asc"}},"composerLock":null},"psalm":{"api-version":"1.0.0","version":"1.2.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/psalm/psalm-1.2.0.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"tool":{"psalm":"^3.0 || ^4.0 || ^5.0"}},"checksum":{"type":"sha-512","value":"4a550c9226d7bca582d7c10bd87cce01190c96398936b1613421640c83df62ed1c6e0d44c1b39635414ea8cf4a892a6458d27590793238add24e7cb5547e6ffd"},"tools":{"psalm":{"version":"5.24.0","url":"https://github.com/vimeo/psalm/releases/download/5.24.0/psalm.phar","requirements":{"php":{"php":"^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0","ext-SimpleXML":"*","ext-ctype":"*","ext-dom":"*","ext-json":"*","ext-libxml":"*","ext-mbstring":"*","ext-tokenizer":"*"}},"checksum":null,"signature":"https://github.com/vimeo/psalm/releases/download/5.24.0/psalm.phar.asc"}},"composerLock":null},"composer-require-checker":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-require-checker/composer-require-checker-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0"},"tool":{"composer-require-checker":"^3.8 || ^4.0"}},"checksum":{"type":"sha-512","value":"d5415bddfe024c5749d894034583882aee4e5c3e1087815d9fdd81cb5e71630f631a0e35de0ff84b97fbbf738c16ece5f83bd8c00695913eb846aa6f04577dc2"},"tools":{"composer-require-checker":{"version":"4.7.1","url":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.7.1/composer-require-checker.phar","requirements":{"php":{"php":"~8.1.0 || ~8.2.0 || ~8.3.0","ext-phar":"*"}},"checksum":null,"signature":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.7.1/composer-require-checker.phar.asc"}},"composerLock":null},"phpmd":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpmd/phpmd-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpmd":"^2.6.1"}},"checksum":{"type":"sha-512","value":"f22280a6dec8dbdd2ec1d83b294f23237fe32c34f4a298e52038e0a7a0074d541635b2b488b1a6098a42d8418a6cd8eb804406ea82b91e362be2b5d11a0915b0"},"tools":{"phpmd":{"version":"2.15.0","url":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar","requirements":{"php":{"php":">=5.3.9","ext-xml":"*"}},"checksum":null,"signature":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar.asc"}},"composerLock":null},"phpcpd":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcpd/phpcpd-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcpd":"^6.0"}},"checksum":{"type":"sha-512","value":"1189ce0bf3fade4cb4241f1d96f915ef8fc7651f4450dc79fdf464ee3d6be3009316f0d423ce2d4af9d76ad50807b7fdf4d77bfa6d9ee2c91d6eda32ea214433"},"tools":{"phpcpd":{"version":"6.0.3","url":"https://phar.phpunit.de/phpcpd-6.0.3.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*"}},"checksum":{"type":"sha-256","value":"2cbaea7cfda1bb4299d863eb075e977c3f49055dd16d88529fae5150d48a84cb"},"signature":"https://phar.phpunit.de/phpcpd-6.0.3.phar.asc"}},"composerLock":null},"phploc":{"api-version":"1.0.0","version":"1.0.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phploc/phploc-1.0.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*","ext-json":"*"},"tool":{"phploc":"^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"}},"checksum":{"type":"sha-512","value":"f67b02d494796adf553cb3dd13ec06c1cb8e53c799954061749424251379541637538199afb3afa3c7a01cabd1cb6f1c53eb621f015dff9644c6c7cbf10c56d1"},"tools":{"phploc":{"version":"7.0.2","url":"https://phar.phpunit.de/phploc-7.0.2.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*","ext-json":"*"}},"checksum":{"type":"sha-256","value":"3d59778ec86faf25fd00e3a329b2f9ad4a3c751ca91601ea7dab70f887b0bf46"},"signature":"https://phar.phpunit.de/phploc-7.0.2.phar.asc"}},"composerLock":null},"phpcs":{"api-version":"1.0.0","version":"1.2.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcs/phpcs-1.2.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcs":"^3.0 || ^2.0","phpcbf":"^3.0 || ^2.0"}},"checksum":{"type":"sha-512","value":"b6ed00306e76068a6af5e3b1dec837724f9e1900ef1049ce88e7ce195b0583524ca33a73613fba13244307a7ca853b6ddaa14ded69f651c3f184ac130bd1aaad"},"tools":{"phpcs":{"version":"3.9.2","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.9.2/phpcs.phar","requirements":{"php":{"php":">=5.4.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.9.2/phpcs.phar.asc"},"phpcbf":{"version":"3.9.2","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.9.2/phpcbf.phar","requirements":{"php":{"php":">=5.4.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.9.2/phpcbf.phar.asc"}},"composerLock":null},"composer-normalize":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-normalize/composer-normalize-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-json":"*"},"tool":{"composer-normalize":"^2.1"}},"checksum":{"type":"sha-512","value":"d9abda440b85d501c58abf9c81bf76f417594b397129215ffa8b777e9bb5e5eda37d7661d661db3c8d11c24f20345bc6fbe56f013b3b9435d459d2b94f086e0f"},"tools":{"composer-normalize":{"version":"2.42.0","url":"https://github.com/ergebnis/composer-normalize/releases/download/2.42.0/composer-normalize.phar","requirements":{"php":{"php":"~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0","ext-json":"*"}},"checksum":null,"signature":"https://github.com/ergebnis/composer-normalize/releases/download/2.42.0/composer-normalize.phar.asc"}},"composerLock":null}},"tools":[]}
\ No newline at end of file
diff --git a/.phpcq.yaml.dist b/.phpcq.yaml.dist
new file mode 100644
index 000000000..6dd1dffd9
--- /dev/null
+++ b/.phpcq.yaml.dist
@@ -0,0 +1,111 @@
+phpcq:
+ repositories:
+ - https://phpcq.github.io/repository/repository.json
+ directories:
+ - src
+ - tests
+ artifact: .phpcq/build
+ composer:
+ autodiscover: false
+
+ plugins:
+ phpunit:
+ version: ^1.0
+ signed: false
+ psalm:
+ version: ^1.0
+ signed: false
+ composer-require-checker:
+ version: ^1.0
+ signed: false
+ phpmd:
+ version: ^1.0
+ signed: false
+ phpcpd:
+ version: ^1.1
+ signed: false
+ phploc:
+ version: ^1.0
+ signed: false
+ phpcs:
+ version: ^1.0
+ signed: false
+ composer-normalize:
+ version: ^1.0
+ signed: false
+ trusted-keys:
+ # composer-require-checker
+ - 033E5F8D801A2F8D
+ # sb@sebastian-bergmann.de
+ - 4AA394086372C20A
+ # psalm
+ - 8A03EA3B385DBAA1
+ - 12CE0F1D262429A5
+ # magl@magll.net
+ - D2CCAC42F6295E7D
+ # PHP_CodeSniffer
+ - 31C7E470E2138192
+ # Composer normalize
+ - C00543248C87FB13
+ # phpmd
+ - A4E55EA12C7C085C
+
+tasks:
+ fix:
+ - composer-normalize-fix
+ - phpcbf
+
+ verify:
+ - composer-require-checker
+ - composer-normalize
+
+ analyze:
+ - phploc
+ - phpcpd
+ - phpmd
+ - phpcs
+ - psalm
+ - phpunit
+
+ default:
+ - verify
+ - analyze
+
+ phpcpd:
+ plugin: phpcpd
+ config:
+ exclude:
+ - tests
+ - src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder
+ - src/CoreBundle/Resources/contao/dca
+
+ phpmd:
+ plugin: phpmd
+ config:
+ ruleset:
+ - ./.phpmd.xml
+
+ composer-require-checker:
+ plugin: composer-require-checker
+ config:
+ config_file: '.composer-require-checker.json'
+
+ phpcs:
+ plugin: phpcs
+ config: &phpcs-config
+ standard: PSR12
+ excluded:
+ - '*/CoreBundle/Resources/contao/dca/*'
+ - '*/CoreBundle/Resources/contao/languages/*'
+ - '*/CoreBundle/Resources/public/*'
+
+ phpcbf:
+ plugin: phpcs
+ config:
+ <<: *phpcs-config
+ fix: true
+
+ composer-normalize-fix:
+ plugin: composer-normalize
+ config:
+ dry_run: false
diff --git a/.phpmd.xml b/.phpmd.xml
new file mode 100644
index 000000000..78eb716f3
--- /dev/null
+++ b/.phpmd.xml
@@ -0,0 +1,37 @@
+
+
+
+ PHPMD rule set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index fdda7c717..91e2c185b 100644
--- a/README.md
+++ b/README.md
@@ -3,14 +3,17 @@
[![Latest Version on Packagist](http://img.shields.io/packagist/v/MetaModels/core.svg)](https://packagist.org/packages/MetaModels/core)
[![Installations via composer per month](http://img.shields.io/packagist/dm/MetaModels/core.svg)](https://packagist.org/packages/MetaModels/core)
-MetaModels
-==========
+# MetaModels
-So what are MetaModels?
------------------------
+Deutsch - s.u.
+
+## So what are MetaModels?
MetaModels are data models you can configure in the Contao Backend. Every MetaModel consists of various attributes of
-certain data types (attribute types are available as extensions and get registered upon installation).
+certain data types (attribute types are available as extensions and get registered upon installation). In addition,
+several MetaModel can be linked to each other via relations as single or multiple selections can be linked with each
+other. This allows very complex data structures to be mapped in Contao, e.g. for product catalogues, employee lists,
+event management, etc. Programming knowledge is generally not necessary for the creation not necessary.
To present the data on the screen (i.e. website, RSS feed, etc.), you define render settings for the MetaModel which
define how the various attribute output shall look like (image sizes, use light boxes, etc.).
@@ -21,10 +24,7 @@ can be nested (AND/OR conditions i.e.) and be of various nature.
Before you start it is helpful to look at the [MetaModels manual](http://metamodels.readthedocs.org/de/latest/index.html).
There you will find actual information about the usage and the installation.
-How to use it.
---------------
-
-### Install
+## Install
You can install MetaModels core with Contao Manager - search "metamodels/core" - or you can use composer
@@ -32,13 +32,16 @@ You can install MetaModels core with Contao Manager - search "metamodels/core" -
Then add all the necessary [attributes, filters or MetaModel extensions](https://extensions.contao.org/?q=metamodels).
+Please [check the manual to see which versions are available](https://metamodels.readthedocs.io/de/latest/manual/install.html#ubersicht-der-versionen)
+and which Contao version works with them. Please also note that the latest version is not always available on Github
+and that a newer version may only be available as an "early adopter" with special access - [see Fundrasing](https://now.metamodel.me/en/supporters/fundraising).
+
For the first evaluation of the possibilities of MetaModels you can also use [metamodels/bundle_start](https://extensions.contao.org/?q=metamodels%252Fbundle_start),
which installs some attributes and filters.
Please do not forget to perform the migration of the database!
-Docs:
------
+## Docs:
* [The official MetaModel Documentation (de)](http://metamodels.readthedocs.org/de/latest/index.html) (Currently the main documentation)
* [The official MetaModel Documentation (en)](http://metamodels.readthedocs.org/en/latest/index.html)
@@ -47,16 +50,14 @@ Docs:
Feel free to contribute the MetaModel Documentation in [EN](https://github.com/MetaModels/docs) or
[DE](https://github.com/MetaModels/docs-de)
-Resources:
-----------
+## Resources:
* [MetaModels Website](https://now.metamodel.me)
* [MetaModels Contao Wiki [DE]](https://de.contaowiki.org/MetaModels)
* [MetaModels Contao Community Subforum [DE]](https://community.contao.org/de/forumdisplay.php?149-MetaModels)
* [MetaModels Channel on Contao Slack #metamodels](https://contao.slack.com/archives/CKGEBDV60)
-History:
---------
+## History:
Metamodels are the replacement for the famous Catalog extension for [Contao CMS](https://github.com/contao/core).
@@ -69,12 +70,51 @@ likely be possible both in implementation and learning curve but that this will
resembling only of the name with it's ancestor.
Therefore, we rebranded everything as "MetaModels".
-Who did it?
------------
+## Who did it?
See the [CONTRIBUTORS.md](https://github.com/MetaModels/core/tree/master/CONTRIBUTORS.md) file.
-Third Party Licenses:
----------------------
+## Third Party Licenses:
Icons: This software uses the [Fugue Icons](http://p.yusukekamiyamane.com)
+
+## Deutsch
+
+## Was ist MetaModells?
+
+MetaModels sind Datenmodelle, die Sie im Contao Backend konfigurieren können. Jedes MetaModel besteht aus verschiedenen
+Attributen von bestimmten Datentypen wie Text, Checkbox, Dateien (Attributtypen sind als Erweiterungen verfügbar und
+werden bei der Installation registriert). Zudem können mehrere MetaModel über Relationen als Einzel- oder Mehrfachauswahl
+miteinander verknüpft werden. Damit lassen sich sehr komplexe Datenstrukturen in Contao abbilden wie z. B. für
+Produktkataloge, Mitarbeiterlisten, Eventmanagements usw. Programmierkenntnisse sind in der Regel für die Erstellung
+nicht notwendig.
+
+Um die Daten auf dem Bildschirm (z. B. Website, RSS-Feed, etc.) darzustellen, definieren Sie Rendereinstellungen für
+das MetaModel, die festlegen, wie die verschiedenen Attributausgaben aussehen sollen (Bildgrößen, Verwendung von
+Lightboxes usw.).
+
+Das Filtern von Daten in Listenansichten erfordert die Konfiguration von Filtereinstellungen. Filtereinstellungen sind
+ein sehr komplexes Thema, da sie verschachtelt (z.B. AND/OR-Bedingungen) und von unterschiedlicher Natur sein können.
+
+Vor dem Start ist es hilfreich, einen Blick in das [MetaModels Handbuch](http://metamodels.readthedocs.org/de/latest/index.html)
+zu werfen. Dort findet man aktuelle Informationen über die Verwendung, Installation und Tipps&Tricks.
+
+## Installation
+
+MetaModels Core kann mit dem Contao Manager installiert werden - suchen Sie nach "metamodels/core" - oder Sie können
+composer verwenden
+
+``php public/contao-manager.phar.php composer require metamodels/core``
+
+Dann fügen Sie alle notwendigen [Attribute, Filter oder MetaModel-Erweiterungen](https://extensions.contao.org/?q=metamodels)
+hinzu, die Sie für ihr Model benötigen.
+
+Bitte [schauen Sie im Handbuch nach, welche Versionen verfügbar sind](https://metamodels.readthedocs.io/de/latest/manual/install.html#ubersicht-der-versionen)
+und welche Contao-Version mit ihnen funktioniert. Bitte beachten Sie auch, dass nicht immer die neueste Version auf
+Github verfügbar ist und dass eine neuere Version möglicherweise nur als "Early Adopter" mit speziellem Zugang
+erhältlich ist - [siehe Fundrasing](https://now.metamodel.me/en/supporters/fundraising).
+
+Für eine erste Evaluierung der Möglichkeiten von MetaModels kann man auch [metamodels/bundle_start](https://extensions.contao.org/?q=metamodels%252Fbundle_start)
+verwenden, das einige Attribute und Filter installiert.
+
+Bitte vergessen Sie nicht, die Migration der Datenbank durchzuführen!
diff --git a/build.default.properties b/build.default.properties
deleted file mode 100644
index 3520e42fc..000000000
--- a/build.default.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#####################################################
-## This project is using the ##
-## PHP code quality project (phpcq) ##
-## ##
-## https://github.com/phpcq/phpcq ##
-#####################################################
-
-phpcs.standard=${basedir}/vendor/phpcq/coding-standard/phpcs/PhpCodeQuality/ruleset.xml
-phpmd.ruleset=${basedir}/vendor/phpcq/coding-standard/phpmd/ruleset.xml
-
-# Exclude known "similar files" from cpd detection - We know these are code smells but can't refactor until 3.0
-# Sadly we can only exclude paths and not files.
-phpcpd.excluded=src/CoreBundle/Resources/contao/dca \
- src/Helper \
- src/CoreBundle/EventListener/DcGeneral/Table/DcaSetting \
- src/CoreBundle/EventListener/DcGeneral/Table/RenderSetting \
- src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder \
-
-# FIXME: phpcs changed the evaluation of ignored files
-# from multiple occurrences of '--ignore' to a comma separated list in 3.0.
-phpcs.excluded=*/Resources/public/*,\
-*/Resources/contao/config/*,\
-*/Resources/contao/dca/*,\
-*/Resources/contao/languages/*
diff --git a/build.xml b/build.xml
deleted file mode 100644
index 61e0fb463..000000000
--- a/build.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
diff --git a/composer.json b/composer.json
index 1cedd33f4..09e923ccd 100644
--- a/composer.json
+++ b/composer.json
@@ -1,19 +1,18 @@
{
"name": "metamodels/core",
"description": "MetaModels core",
+ "license": "LGPL-3.0-or-later",
+ "type": "contao-bundle",
"keywords": [
"contao",
"metamodels",
"core"
],
- "type": "contao-bundle",
- "homepage": "http://now.metamodel.me/",
- "license": "LGPL-3.0-or-later",
"authors": [
{
"name": "Christian Schiffler",
"email": "c.schiffler@cyberspectrum.de",
- "homepage": "http://www.cyberspectrum.de",
+ "homepage": "https://www.cyberspectrum.de",
"role": "Developer"
},
{
@@ -22,6 +21,7 @@
"role": "Developer"
}
],
+ "homepage": "https://now.metamodel.me/",
"support": {
"email": "mail@metamodel.me",
"issues": "https://github.com/MetaModels/core/issues",
@@ -30,33 +30,46 @@
"source": "https://github.com/MetaModels/core"
},
"require": {
- "php": "^7.4",
- "contao-community-alliance/dc-general": "^2.2",
- "contao-community-alliance/events-contao-bindings": "^4.9",
- "contao-community-alliance/meta-palettes": "~2.0",
- "contao-community-alliance/translator": "^2.2",
- "contao-community-alliance/url-builder": "~1.3",
- "contao/core-bundle": "^4.9.0, <4.13.0",
- "discordier/justtextwidgets": "^1.2",
- "doctrine/cache": "~1.6",
- "menatwork/contao-multicolumnwizard-bundle": "^3.4",
- "symfony/asset": "^4.4.6",
- "symfony/config": "^4.4.6",
- "symfony/dependency-injection": "^4.4.6",
- "symfony/event-dispatcher": "^4.4.6",
- "symfony/filesystem": "^4.4.6",
- "symfony/finder": "^4.4.6",
+ "php": "^8.1",
+ "ext-dom": "*",
+ "contao-community-alliance/dc-general": "^2.3.10",
+ "contao-community-alliance/events-contao-bindings": "^4.13.1",
+ "contao-community-alliance/meta-palettes": "^2.0.10",
+ "contao-community-alliance/translator": "^2.4.2",
+ "contao-community-alliance/url-builder": "^1.3.3",
+ "contao/core-bundle": "^4.13.5 <5.0",
+ "discordier/justtextwidgets": "^1.3",
+ "doctrine/cache": "^2.1",
+ "doctrine/dbal": "^3.6.0",
+ "doctrine/orm": "^2.16.0",
+ "menatwork/contao-multicolumnwizard-bundle": "^3.6.7",
+ "psr/container": "^1.1.2",
+ "psr/log": "^2.0.0",
+ "symfony/asset": "^5.4",
+ "symfony/cache": "^5.4",
+ "symfony/config": "^5.4",
+ "symfony/console": "^5.4",
+ "symfony/dependency-injection": "^5.4",
+ "symfony/deprecation-contracts": "^3.3.0",
+ "symfony/event-dispatcher": "^5.4",
+ "symfony/event-dispatcher-contracts": "^2.5.2",
+ "symfony/filesystem": "^5.4",
+ "symfony/finder": "^5.4",
+ "symfony/http-foundation": "^5.4",
+ "symfony/http-kernel": "^5.4",
+ "symfony/routing": "^5.4",
+ "symfony/security-core": "^5.4",
+ "symfony/service-contracts": "^2.5.2",
+ "symfony/translation": "^5.4",
+ "symfony/translation-contracts": "^2.5.2",
"terminal42/service-annotation-bundle": "^1.0",
- "webmozart/path-util": "~2.3"
+ "twig/twig": "^3.7.0"
},
"require-dev": {
+ "contao/manager-bundle": "^4.13.0 <5.0",
"contao/manager-plugin": "^2.8",
- "contao/manager-bundle": "^4.9.0, <4.13.0",
- "doctrine/doctrine-bundle": "^1.8",
- "phpcq/all-tasks": "^1.3"
- },
- "conflict": {
- "contao/manager-bundle": "4.9.27"
+ "doctrine/doctrine-bundle": "^1.8.1 || ^2.8.3",
+ "phpcq/runner-bootstrap": "^1.0@dev"
},
"autoload": {
"psr-4": {
@@ -68,20 +81,24 @@
"MetaModels\\Test\\": "tests/"
}
},
- "extra": {
- "contao-manager-plugin": "MetaModels\\CoreBundle\\ContaoManager\\Plugin",
- "contao": {
- "runonce": [
- "runonce/runonce.php"
- ]
- }
- },
"config": {
"allow-plugins": {
"contao-components/installer": false,
"contao/manager-plugin": false,
- "php-http/discovery": true
+ "php-http/discovery": false
},
"sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-feature/2.3.0": "2.3.x-dev",
+ "dev-feature/translation_loader": "2.3.x-trans-dev"
+ },
+ "contao": {
+ "runonce": [
+ "runonce/runonce.php"
+ ]
+ },
+ "contao-manager-plugin": "MetaModels\\CoreBundle\\ContaoManager\\Plugin"
}
}
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 000000000..6878801dc
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Attribute/AbstractAttributeTypeFactory.php b/src/Attribute/AbstractAttributeTypeFactory.php
index 11b57316f..3c13bb402 100644
--- a/src/Attribute/AbstractAttributeTypeFactory.php
+++ b/src/Attribute/AbstractAttributeTypeFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,8 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author David Molineus
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -34,21 +35,21 @@ abstract class AbstractAttributeTypeFactory implements IAttributeTypeFactory
*
* @var string
*/
- protected $typeName;
+ protected $typeName = '';
/**
* The name of the attribute class of this type.
*
- * @var string
+ * @var class-string
*/
- protected $typeClass;
+ protected $typeClass = IAttribute::class;
/**
- * The icon representing this attributy type.
+ * The icon representing this attribute type.
*
* @var string
*/
- protected $typeIcon;
+ protected $typeIcon = '';
/**
* {@inheritdoc}
@@ -89,7 +90,7 @@ protected function __construct()
*/
public function isTranslatedType()
{
- return in_array('MetaModels\Attribute\ITranslated', class_implements($this->typeClass, true));
+ return \in_array('MetaModels\Attribute\ITranslated', \class_implements($this->typeClass), true);
}
/**
@@ -99,7 +100,7 @@ public function isTranslatedType()
*/
public function isSimpleType()
{
- return in_array('MetaModels\Attribute\ISimple', class_implements($this->typeClass, true));
+ return \in_array('MetaModels\Attribute\ISimple', \class_implements($this->typeClass), true);
}
/**
@@ -109,6 +110,6 @@ public function isSimpleType()
*/
public function isComplexType()
{
- return in_array('MetaModels\Attribute\IComplex', class_implements($this->typeClass, true));
+ return \in_array('MetaModels\Attribute\IComplex', \class_implements($this->typeClass), true);
}
}
diff --git a/src/Attribute/AttributeFactory.php b/src/Attribute/AttributeFactory.php
index ad33a7277..d00adbe6f 100644
--- a/src/Attribute/AttributeFactory.php
+++ b/src/Attribute/AttributeFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2021 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2021 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -32,15 +33,19 @@
* This is the implementation of the Field factory to query instances of fields.
*
* Usually this is only used internally by {@link MetaModels\Factory}
+ *
+ * @psalm-suppress DeprecatedInterface
*/
class AttributeFactory implements IAttributeFactory
{
/**
* The service container.
*
- * @var IMetaModelsServiceContainer
+ * @var IMetaModelsServiceContainer|null
+ *
+ * @psalm-suppress DeprecatedInterface
*/
- protected $serviceContainer;
+ protected $serviceContainer = null;
/**
* The event dispatcher.
@@ -54,7 +59,7 @@ class AttributeFactory implements IAttributeFactory
*
* @var IAttributeTypeFactory[]
*/
- protected $typeFactories = array();
+ protected $typeFactories = [];
/**
* Create a new instance.
@@ -70,11 +75,12 @@ public function __construct(EventDispatcherInterface $eventDispatcher)
* Set the service container.
*
* @param IMetaModelsServiceContainer $serviceContainer The service container to use.
- *
* @param bool $deprecationNotice Determine deprecated notice.
*
* @return AttributeFactory
*
+ * @psalm-suppress DeprecatedInterface
+ *
* @deprecated The service container will get removed, use the symfony service container instead.
*/
public function setServiceContainer(IMetaModelsServiceContainer $serviceContainer, $deprecationNotice = true)
@@ -113,6 +119,8 @@ public function setServiceContainer(IMetaModelsServiceContainer $serviceContaine
*
* @return IMetaModelsServiceContainer
*
+ * @psalm-suppress DeprecatedInterface
+ *
* @deprecated The service container will get removed, use the symfony service container instead.
*/
public function getServiceContainer()
@@ -123,6 +131,10 @@ public function getServiceContainer()
E_USER_DEPRECATED
);
// @codingStandardsIgnoreEnd
+ if (null === $this->serviceContainer) {
+ throw new \RuntimeException('The deprecated service container has not been set anymore.');
+ }
+
return $this->serviceContainer;
}
@@ -130,7 +142,6 @@ public function getServiceContainer()
* Create an attribute instance from an information array.
*
* @param array $information The attribute information.
- *
* @param IMetaModel $metaModel The MetaModel instance for which the attribute shall be created.
*
* @return IAttribute|null
@@ -143,8 +154,11 @@ public function createAttribute($information, $metaModel)
if ($event->getAttribute()) {
return $event->getAttribute();
}
+ if (null === ($type = $information['type'] ?? null)) {
+ return null;
+ }
- $factory = $this->getTypeFactory($information['type']);
+ $factory = $this->getTypeFactory($type);
if (!$factory) {
return null;
@@ -175,15 +189,18 @@ public function addTypeFactory(IAttributeTypeFactory $typeFactory)
*/
public function getTypeFactory($typeFactory)
{
- return isset($this->typeFactories[(string) $typeFactory]) ? $this->typeFactories[(string) $typeFactory] : null;
+ return $this->typeFactories[$typeFactory] ?? null;
}
/**
* {@inheritdoc}
*/
- public function attributeTypeMatchesFlags($name, $flags)
+ public function attributeTypeMatchesFlags($factory, $flags)
{
- $factory = $this->getTypeFactory($name);
+ $factory = $this->getTypeFactory($factory);
+ if (null === $factory) {
+ return false;
+ }
// Shortcut, if all are valid, return all. :)
if ($flags === self::FLAG_ALL) {
@@ -204,8 +221,8 @@ public function getTypeNames($flags = false)
$flags = self::FLAG_ALL;
}
- $result = array();
- foreach (array_keys($this->typeFactories) as $name) {
+ $result = [];
+ foreach (\array_keys($this->typeFactories) as $name) {
if (!$this->attributeTypeMatchesFlags($name, $flags)) {
continue;
}
@@ -249,6 +266,6 @@ public function createAttributesForMetaModel($metaModel)
*/
public function getIconForType($type)
{
- return isset($this->typeFactories[(string) $type]) ? $this->typeFactories[(string) $type]->getTypeIcon() : null;
+ return isset($this->typeFactories[$type]) ? $this->typeFactories[$type]->getTypeIcon() : '';
}
}
diff --git a/src/Attribute/Base.php b/src/Attribute/Base.php
index 61032c098..681b40da5 100644
--- a/src/Attribute/Base.php
+++ b/src/Attribute/Base.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -19,13 +19,14 @@
* @author David Molineus
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\Attribute;
+use MetaModels\Helper\LocaleUtil;
use MetaModels\IMetaModel;
use MetaModels\ITranslatedMetaModel;
use MetaModels\Render\Setting\ISimple as ISimpleRenderSetting;
@@ -38,20 +39,25 @@
* This class is the reference implementation for {@link IMetaModelAttribute}.
*
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
abstract class Base implements IAttribute
{
+ use ManagedAttributeTrait;
+
/**
* The MetaModel instance this object belongs to.
*
* @var IMetaModel
*/
- private $metaModel;
+ private IMetaModel $metaModel;
/**
* The meta information of this attribute.
*
- * @var array
+ * @var array
*/
protected $arrData = [];
@@ -61,17 +67,20 @@ abstract class Base implements IAttribute
* Note that you should not use this directly but use the factory classes to instantiate attributes.
*
* @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
- *
* @param array $arrData The information array, for attribute information, refer to documentation of
* table tl_metamodel_attribute and documentation of the certain attribute classes
* for information what values are understood.
*/
- public function __construct(IMetaModel $objMetaModel, $arrData = array())
+ public function __construct(IMetaModel $objMetaModel, $arrData = [])
{
+ /**
+ * @psalm-suppress DeprecatedMethod
+ * @psalm-suppress TooManyArguments
+ */
if (!($objMetaModel instanceof ITranslatedMetaModel) && $objMetaModel->isTranslated(false)) {
// @codingStandardsIgnoreStart
@\trigger_error(
- sprintf(
+ \sprintf(
'Support for translated "\MetaModel\IMetamodel" instances is deprecated since MetaModels 2.2 ' .
'and to be removed in 3.0. The MetaModel "%s" must implement "\MetaModels\ITranslatedMetaModel".',
$objMetaModel->getTableName()
@@ -82,7 +91,7 @@ public function __construct(IMetaModel $objMetaModel, $arrData = array())
}
// Meta information.
- foreach (array_intersect($this->getAttributeSettingNames(), array_keys($arrData)) as $strSettingName) {
+ foreach (\array_intersect($this->getAttributeSettingNames(), \array_keys($arrData)) as $strSettingName) {
$this->set($strSettingName, $arrData[$strSettingName]);
}
$this->metaModel = $objMetaModel;
@@ -103,28 +112,32 @@ public function __construct(IMetaModel $objMetaModel, $arrData = array())
*/
protected function parameterMask($parameters)
{
- return implode(',', array_fill(0, count($parameters), '?'));
+ return \implode(',', \array_fill(0, \count($parameters), '?'));
}
/**
- * Retrieve the human readable name (or title) from the attribute.
+ * Retrieve the human-readable name (or title) from the attribute.
*
* If the MetaModel is translated, the currently active language is used,
* with properly falling back to the defined fallback language.
*
- * @return string the human readable name
+ * @return string the human-readable name
*/
public function getName()
{
- if (is_array($this->arrData['name'])) {
+ if (isset($this->arrData['name']) && \is_array($this->arrData['name'])) {
$metaModel = $this->getMetaModel();
- return $this->getLangValue(
- $this->get('name'),
+ $langValue = $this->get('name');
+ assert(\is_array($langValue) || \is_string($langValue));
+ /** @psalm-suppress DeprecatedMethod */
+ return (string) $this->getLangValue(
+ $langValue,
($metaModel instanceof ITranslatedMetaModel)
? $metaModel->getLanguage() : $metaModel->getActiveLanguage()
) ?: $this->getColName();
}
- return $this->arrData['name'] ?: $this->getColName();
+
+ return $this->arrData['name'] ?? $this->getColName();
}
/**
@@ -133,51 +146,68 @@ public function getName()
* If the language is not contained within the value array, the fallback language from the parenting
* {@link IMetaModel} instance is tried as well.
*
- * @param array $arrValues The array holding all language values in the form array('langcode' => $varValue).
+ * @param array|string $arrValues The array holding all language values in the form
+ * array('langcode' => $varValue).
+ * @param string|null $strLangCode The language code of the language to fetch.
*
- * @param string $strLangCode The language code of the language to fetch.
- *
- * @return mixed|null the value for the given language or the fallback language, NULL if neither is present.
+ * @return string|null the value for the given language or the fallback language, NULL if neither is present.
*
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- protected function getLangValue($arrValues, $strLangCode = null)
+ protected function getLangValue($arrValues, $strLangCode = null): ?string
{
- $metaModel = $this->getMetaModel();
// Not a valid lookup array, exit.
- if (!is_array($arrValues)) {
+ if (!\is_array($arrValues)) {
return $arrValues;
}
+ $metaModel = $this->getMetaModel();
+ // Not translated, exit.
+ /**
+ * @psalm-suppress DeprecatedMethod
+ * @psalm-suppress TooManyArguments
+ */
+ if (!($metaModel instanceof ITranslatedMetaModel) && !$metaModel->isTranslated(false)) {
+ return reset($arrValues);
+ }
+
if (null === $strLangCode) {
// @codingStandardsIgnoreStart
@\trigger_error(
- sprintf(
+ \sprintf(
'Not passing the language code to "%s" is deprecated since MetaModels 2.2 and will fail in 3.0 ',
__METHOD__
),
E_USER_DEPRECATED
);
// @codingStandardsIgnoreEnd
- $strLangCode = \str_replace('-', '_', $GLOBALS['TL_LANGUAGE']);
- }
- // Not translated, exit.
- if (!($metaModel instanceof ITranslatedMetaModel) && !$metaModel->isTranslated(false)) {
- return $arrValues;
+ // @deprecated usage of TL_LANGUAGE - remove for Contao 5.0.
+ // In future versions we try to read from locale request, otherwise we raise an exception.
+ /** @psalm-suppress DeprecatedMethod */
+ $strLangCode = LocaleUtil::formatAsLocale(
+ $GLOBALS['TL_LANGUAGE']
+ ?? (($this->metaModel instanceof ITranslatedMetaModel)
+ ? $this->metaModel->getLanguage()
+ : $this->metaModel->getActiveLanguage())
+ );
}
- if (array_key_exists($strLangCode, $arrValues)) {
+ // If empty, use main-language.
+ if (\array_key_exists($strLangCode, $arrValues) && '' !== $arrValues[$strLangCode]) {
return $arrValues[$strLangCode];
}
+
// Language code not set, use fallback.
if ($metaModel instanceof ITranslatedMetaModel) {
$strLangCode = $metaModel->getMainLanguage();
} else {
- $strLangCode = $metaModel->getFallbackLanguage();
+ /** @psalm-suppress DeprecatedMethod */
+ $strLangCode = (string) $metaModel->getFallbackLanguage();
}
- if (array_key_exists($strLangCode, $arrValues)) {
+
+ if (\array_key_exists($strLangCode, $arrValues)) {
return $arrValues[$strLangCode];
}
@@ -187,30 +217,36 @@ protected function getLangValue($arrValues, $strLangCode = null)
/**
* Hook additional attribute formatter that want to format the value.
*
- * @param array $arrBaseFormatted The current result array. The keys "raw" and "text" are always
- * populated.
- *
- * @param array $arrRowData The Raw values from the database.
- *
- * @param string $strOutputFormat The output format to use.
- *
- * @param ISimpleRenderSetting $objSettings The output format settings.
+ * @param array $arrBaseFormatted The current result array. The keys "raw" and "text" are always
+ * populated.
+ * @param array $arrRowData The Raw values from the database.
+ * @param string $strOutputFormat The output format to use.
+ * @param ISimpleRenderSetting|null $objSettings The output format settings.
*
* @return mixed
*
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
+ *
+ * @deprecated This will get removed in 3.0.
*/
public function hookAdditionalFormatters($arrBaseFormatted, $arrRowData, $strOutputFormat, $objSettings)
{
$arrResult = $arrBaseFormatted;
- if (isset($GLOBALS['METAMODEL_HOOKS']['parseValue']) && is_array($GLOBALS['METAMODEL_HOOKS']['parseValue'])) {
+ if (isset($GLOBALS['METAMODEL_HOOKS']['parseValue']) && \is_array($GLOBALS['METAMODEL_HOOKS']['parseValue'])) {
+ // @codingStandardsIgnoreStart
+ @trigger_error(
+ '"' .__METHOD__ . '" is deprecated and will get removed.',
+ E_USER_DEPRECATED
+ );
+ // @codingStandardsIgnoreEnd
+
foreach ($GLOBALS['METAMODEL_HOOKS']['parseValue'] as $callback) {
- list($strClass, $strMethod) = $callback;
+ [$strClass, $strMethod] = $callback;
- $objCallback = (in_array('getInstance', get_class_methods($strClass)))
- ? call_user_func(array($strClass, 'getInstance'))
+ $objCallback = (\in_array('getInstance', \get_class_methods($strClass)))
+ ? \call_user_func(array($strClass, 'getInstance'))
: new $strClass();
$arrResult = $objCallback->$strMethod(
@@ -230,24 +266,22 @@ public function hookAdditionalFormatters($arrBaseFormatted, $arrRowData, $strOut
* When rendered via a template, this populates the template with values.
*
* @param Template $objTemplate The Template instance to populate.
- *
* @param array $arrRowData The row data for the current item.
- *
* @param ISimpleRenderSetting $objSettings The render settings to use for this attribute.
*
* @return void
*/
protected function prepareTemplate(Template $objTemplate, $arrRowData, $objSettings)
{
+ $additionalClass = (string) $objSettings->get('additional_class');
+
$objTemplate->setData(
[
'attribute' => $this,
'settings' => $objSettings,
'row' => $arrRowData,
- 'raw' => $arrRowData[$this->getColName()],
- 'additional_class' => $objSettings->get('additional_class')
- ? ' ' . $objSettings->get('additional_class')
- : ''
+ 'raw' => ($arrRowData[$this->getColName()] ?? null),
+ 'additional_class' => $additionalClass ? ' ' . $additionalClass : ''
]
);
}
@@ -257,7 +291,10 @@ protected function prepareTemplate(Template $objTemplate, $arrRowData, $objSetti
*/
public function getColName()
{
- return $this->arrData['colname'];
+ $colName = $this->arrData['colname'];
+ \assert(\is_string($colName));
+
+ return $colName;
}
/**
@@ -273,7 +310,7 @@ public function getMetaModel()
*/
public function get($strKey)
{
- return isset($this->arrData[$strKey]) ? $this->arrData[$strKey] : null;
+ return $this->arrData[$strKey] ?? null;
}
/**
@@ -282,8 +319,8 @@ public function get($strKey)
public function set($strKey, $varValue)
{
if (in_array($strKey, $this->getAttributeSettingNames())) {
- if (!is_array($varValue) && (substr($varValue, 0, 2) == 'a:')) {
- $unSerialized = unserialize($varValue);
+ if (null !== $varValue && !\is_array($varValue) && (\str_starts_with($varValue, 'a:'))) {
+ $unSerialized = \unserialize($varValue);
}
if (isset($unSerialized) && is_array($unSerialized)) {
@@ -298,28 +335,52 @@ public function set($strKey, $varValue)
/**
* {@inheritdoc}
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function handleMetaChange($strMetaName, $varNewValue)
{
- // By default we accept any change of meta information.
+ // By default, we accept any change of meta information.
$this->set($strMetaName, $varNewValue);
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+ return $this;
+ }
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
+
return $this;
}
/**
* {@inheritdoc}
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function destroyAUX()
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
// No-op.
}
/**
* {@inheritdoc}
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function initializeAUX()
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
// No-op.
}
@@ -340,7 +401,9 @@ public function getAttributeSettingNames()
'isvariant',
// Settings originating from tl_metamodel_dcasetting.
'tl_class',
- 'readonly'
+ 'readonly',
+ 'be_template',
+ 'fe_template',
];
}
@@ -352,15 +415,21 @@ public function getAttributeSettingNames()
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- private function setLanguageStrings()
+ private function setLanguageStrings(): void
{
// Only overwrite the language if not already set.
if (empty($GLOBALS['TL_LANG'][$this->getMetaModel()->getTableName()][$this->getColName()])) {
- $GLOBALS['TL_LANG'][$this->getMetaModel()->getTableName()][$this->getColName()] = array
- (
- $this->getLangValue($this->get('name'), \str_replace('-', '_', $GLOBALS['TL_LANGUAGE'])),
- $this->getLangValue($this->get('description'), \str_replace('-', '_', $GLOBALS['TL_LANGUAGE'])),
- );
+ // @deprecated usage of TL_LANGUAGE - remove for Contao 5.0.
+ /** @psalm-suppress DeprecatedMethod */
+ $language = LocaleUtil::formatAsLocale($GLOBALS['TL_LANGUAGE'] ?? (
+ ($this->metaModel instanceof ITranslatedMetaModel)
+ ? $this->metaModel->getLanguage()
+ : $this->metaModel->getActiveLanguage()));
+
+ $GLOBALS['TL_LANG'][$this->getMetaModel()->getTableName()][$this->getColName()] = [
+ $this->getLangValue($this->get('name'), $language),
+ $this->getLangValue($this->get('description'), $language),
+ ];
}
}
@@ -386,6 +455,7 @@ private function getBaseDefinition()
}
if (!isset($definition['label'])) {
+ /** @psalm-suppress UnsupportedReferenceUsage */
$definition['label'] = &$GLOBALS['TL_LANG'][$tableName][$this->getColName()];
}
@@ -411,7 +481,6 @@ private function isAllowedValue($name)
* Extract an override value.
*
* @param string $name The name of the value.
- *
* @param array $overrides The overrides containing the values to be overridden.
*
* @return mixed
@@ -429,7 +498,6 @@ protected function getOverrideValue($name, $overrides)
* Extract an override value.
*
* @param array $fieldDefinition The field definition.
- *
* @param array $overrides The overrides containing the values to be overridden.
*
* @return array
@@ -454,11 +522,14 @@ private function setBaseEval($fieldDefinition, $overrides)
'spaceToUnderscore',
'includeBlankOption',
'submitOnChange',
- 'readonly'
+ 'readonly',
+ 'be_template',
+ 'fe_template',
];
foreach ($names as $name) {
- if (empty($fieldDefinition['eval'][$name])
+ if (
+ empty($fieldDefinition['eval'][$name])
&& ($value = $this->getOverrideValue($name, $overrides))
) {
$fieldDefinition['eval'][$name] = $value;
@@ -476,14 +547,14 @@ private function setBaseEval($fieldDefinition, $overrides)
/**
* {@inheritdoc}
*/
- public function getFieldDefinition($arrOverrides = array())
+ public function getFieldDefinition($arrOverrides = [])
{
$arrFieldDef = $this->setBaseEval($this->getBaseDefinition(), $arrOverrides);
if ($this->isAllowedValue('trailingSlash')) {
- $trailingSlash = $this->getOverrideValue('trailingSlash', $arrOverrides);
- if ($trailingSlash != 2) {
- $arrFieldDef['eval']['trailingSlash'] = (bool) $arrOverrides['trailingSlash'];
+ $trailingSlash = (int) $this->getOverrideValue('trailingSlash', $arrOverrides);
+ if ($trailingSlash !== 2) {
+ $arrFieldDef['eval']['trailingSlash'] = (bool) ($arrOverrides['trailingSlash'] ?? false);
}
}
@@ -508,14 +579,12 @@ public function getFieldDefinition($arrOverrides = array())
*/
public function getItemDCA($arrOverrides = [])
{
- $arrReturn = [
- 'fields' => array_merge(
+ return [
+ 'fields' => \array_merge(
[$this->getColName() => $this->getFieldDefinition($arrOverrides)],
- (array) $GLOBALS['TL_DCA'][$this->getMetaModel()->getTableName()]['fields'][$this->getColName()]
+ (array) ($GLOBALS['TL_DCA'][$this->getMetaModel()->getTableName()]['fields'][$this->getColName()] ?? [])
),
];
-
- return $arrReturn;
}
/**
@@ -539,9 +608,7 @@ public function widgetToValue($varValue, $itemId)
*/
public function getDefaultRenderSettings()
{
- $objSetting = new Simple(['template' => 'mm_attr_' . $this->get('type')]);
-
- return $objSetting;
+ return new Simple(['template' => 'mm_attr_' . ($this->get('type') ?? '')]);
}
/**
@@ -549,12 +616,9 @@ public function getDefaultRenderSettings()
*/
public function parseValue($arrRowData, $strOutputFormat = 'text', $objSettings = null)
{
- $arrResult = ['raw' => $arrRowData[$this->getColName()]];
-
- /** @var ISimpleRenderSetting $objSettings */
- if ($objSettings && $objSettings->get('template')) {
- $strTemplate = $objSettings->get('template');
+ $arrResult = ['raw' => ($arrRowData[$this->getColName()] ?? null)];
+ if ($objSettings && ($strTemplate = (string) $objSettings->get('template'))) {
$objTemplate = new Template($strTemplate);
$this->prepareTemplate($objTemplate, $arrRowData, $objSettings);
@@ -566,13 +630,13 @@ public function parseValue($arrRowData, $strOutputFormat = 'text', $objSettings
// Text rendering is mandatory, try with the current setting,
// upon exception, try again with the default settings, as the template name might have changed.
- // if this fails again, we are definately out of luck and bail the exception.
+ // if this fails again, we are definitely out of luck and bail the exception.
try {
$arrResult['text'] = $objTemplate->parse('text', true);
} catch (\Exception $e) {
$objSettingsFallback = $this->getDefaultRenderSettings()->setParent($objSettings->getParent());
- $objTemplate = new Template($objSettingsFallback->get('template'));
+ $objTemplate = new Template($objSettingsFallback->get('template') ?? '');
$this->prepareTemplate($objTemplate, $arrRowData, $objSettingsFallback);
$arrResult['text'] = $objTemplate->parse('text', true);
@@ -583,6 +647,7 @@ public function parseValue($arrRowData, $strOutputFormat = 'text', $objSettings
}
// HOOK: apply additional formatters to attribute.
+ /** @psalm-suppress DeprecatedMethod */
$arrResult = $this->hookAdditionalFormatters($arrResult, $arrRowData, $strOutputFormat, $objSettings);
return $arrResult;
@@ -598,7 +663,7 @@ public function getFilterUrlValue($varValue)
// We are parsing as text here as this was the way before this method was implemented. See #216.
$arrResult = $this->parseValue([$this->getColName() => $varValue], 'text');
- return $arrResult['text'];
+ return (string) $arrResult['text'];
}
/**
@@ -649,7 +714,7 @@ public function filterLessThan($varValue, $blnInclusive = false)
*/
public function filterNotEqual($varValue)
{
- return array_merge($this->filterLessThan($varValue), $this->filterGreaterThan($varValue));
+ return \array_merge($this->filterLessThan($varValue) ?? [], $this->filterGreaterThan($varValue) ?? []);
}
/**
diff --git a/src/Attribute/BaseSimple.php b/src/Attribute/BaseSimple.php
index b16d135c3..9d3666c92 100644
--- a/src/Attribute/BaseSimple.php
+++ b/src/Attribute/BaseSimple.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -18,7 +18,7 @@
* @author Sven Baumann
* @author David Molineus
* @author Marc Reimann
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -26,10 +26,17 @@
namespace MetaModels\Attribute;
use Contao\System;
+use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Exception;
use MetaModels\Helper\TableManipulator;
use MetaModels\IMetaModel;
+use function array_map;
+use function is_array;
+use function serialize;
+use function str_replace;
+
/**
* Reference implementation for Simple attributes.
* Simple fields are fields that only consist of one column in the metamodel table and therefore do not need
@@ -39,6 +46,8 @@
*/
class BaseSimple extends Base implements ISimple
{
+ use ManagedAttributeTrait;
+
/**
* Database connection.
*
@@ -58,13 +67,13 @@ class BaseSimple extends Base implements ISimple
*
* Note that you should not use this directly but use the factory classes to instantiate attributes.
*
- * @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
- * @param array $arrData The information array, for attribute information, refer to
- * documentation of table tl_metamodel_attribute and documentation of the
- * certain attribute classes for information what values are understood.
- * @param Connection $connection The database connection.
- *
- * @param TableManipulator $tableManipulator Table manipulator instance.
+ * @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
+ * @param array $arrData The information array, for attribute information, refer to
+ * documentation of table tl_metamodel_attribute and documentation
+ * of the certain attribute classes for information what values are
+ * understood.
+ * @param Connection|null $connection The database connection.
+ * @param TableManipulator|null $tableManipulator Table manipulator instance.
*/
public function __construct(
IMetaModel $objMetaModel,
@@ -82,7 +91,9 @@ public function __construct(
);
// @codingStandardsIgnoreEnd
$connection = System::getContainer()->get('database_connection');
+ assert($connection instanceof Connection);
}
+ $this->connection = $connection;
if (null === $tableManipulator) {
// @codingStandardsIgnoreStart
@@ -93,9 +104,8 @@ public function __construct(
// @codingStandardsIgnoreEnd
$tableManipulator = System::getContainer()->get('metamodels.table_manipulator');
+ assert($tableManipulator instanceof TableManipulator);
}
-
- $this->connection = $connection;
$this->tableManipulator = $tableManipulator;
}
@@ -104,26 +114,38 @@ public function __construct(
*
* This tells the attribute to perform any actions that must be done to correctly initialize the new value
* and to perform any action to undo the changes that had been done for the previous value.
- * i.e.: when an attribute type needs columns in an an auxiliary table, these will have to be updated herein.
+ * i.e.: when an attribute type needs columns in an auxiliary table, these will have to be updated herein.
*
* This method may throw an exception, when the new value is invalid or any problems appear, the MetaModelAttribute
* will then keep the old meta value.
*
* @param string $strMetaName Name of the meta information that shall be updated.
+ * @param mixed $varNewValue The new value for this meta information.
*
- * @param mixed $varNewValue The new value for this meta information.
+ * @return IAttribute The instance of this attribute, to support chaining.
*
- * @return \MetaModels\Attribute\IAttribute The instance of this attribute, to support chaining.
+ * @throws Exception
+ *
+ * @deprecated Implement schema generators instead.
*/
public function handleMetaChange($strMetaName, $varNewValue)
{
- // By default we accept any change of meta information.
- if ($strMetaName == 'colname') {
- if ($this->get($strMetaName) != $varNewValue) {
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+ return $this;
+ }
+
+ // By default, we accept any change of meta information.
+ if ($strMetaName === 'colname') {
+ if ($this->get($strMetaName) !== $varNewValue) {
+ /** @psalm-suppress DeprecatedMethod */
$this->renameColumn($varNewValue);
}
+
return $this;
}
+
+ /** @psalm-suppress DeprecatedMethod */
return parent::handleMetaChange($strMetaName, $varNewValue);
}
@@ -133,6 +155,8 @@ public function handleMetaChange($strMetaName, $varNewValue)
* @param mixed $arrValues The values to be stored into database. Mapping is item id=>value.
*
* @return void
+ *
+ * @throws Exception
*/
public function setDataFor($arrValues)
{
@@ -146,12 +170,14 @@ public function setDataFor($arrValues)
->set('t.' . $strColName, ':' . $strColName)
->setParameter($strColName, is_array($varData) ? serialize($varData) : $varData)
->setParameter('id', $intId)
- ->execute();
+ ->executeQuery();
}
}
/**
* {@inheritDoc}
+ *
+ * @throws Exception
*/
public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
{
@@ -168,25 +194,26 @@ public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
->where('t.id IN (:ids)')
->groupBy('t.' . $strCol)
->orderBy('MIN(FIELD(t.id, :ids))')
- ->setParameter('ids', $idList, Connection::PARAM_STR_ARRAY)
- ->execute();
+ ->setParameter('ids', $idList, ArrayParameterType::STRING)
+ ->executeQuery();
} else {
$statement = $this->connection->createQueryBuilder()
->select('t.' . $strCol . ', COUNT(t.' . $strCol . ') as mm_count')
->from($this->getMetaModel()->getTableName(), 't')
->groupBy('t.' . $strCol)
->orderBy('t.' . $strCol)
- ->execute();
+ ->executeQuery();
}
$arrResult = [];
- while ($objRow = $statement->fetch(\PDO::FETCH_OBJ)) {
+ while ($objRow = $statement->fetchAssociative()) {
if (is_array($arrCount)) {
- $arrCount[$objRow->$strCol] = $objRow->mm_count;
+ $arrCount[$objRow[$strCol]] = $objRow['mm_count'];
}
- $arrResult[$objRow->$strCol] = $objRow->$strCol;
+ $arrResult[$objRow[$strCol]] = $objRow[$strCol];
}
+
return $arrResult;
}
@@ -194,20 +221,23 @@ public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
* {@inheritdoc}
*
* This base implementation does a plain SQL sort by native value as defined by MySQL.
+ *
+ * @throws Exception
*/
public function sortIds($idList, $strDirection)
{
// Base implementation, do a simple sorting on given column.
- $idList = $this->connection->createQueryBuilder()
+ $statement = $this->connection
+ ->createQueryBuilder()
->select('t.id')
->from($this->getMetaModel()->getTableName(), 't')
->where('t.id IN (:ids)')
- ->setParameter('ids', $idList, Connection::PARAM_STR_ARRAY)
+ ->setParameter('ids', $idList, ArrayParameterType::STRING)
->orderBy('t.' . $this->getColName(), $strDirection)
- ->execute()
- ->fetchAll(\PDO::FETCH_COLUMN, 'id');
+ ->executeQuery();
- return $idList;
+ // Return value list as list, parent function wants a list so we make a cast.
+ return array_map(static fn (mixed $value) => (string) $value, $statement->fetchFirstColumn());
}
/**
@@ -218,19 +248,25 @@ public function sortIds($idList, $strDirection)
*
* @param string $strPattern The text to search for. This may contain wildcards.
*
- * @return int[] the ids of matching items.
+ * @return list The ids of matching items.
+ *
+ * @throws Exception
*/
public function searchFor($strPattern)
{
// Base implementation, do a simple search on given column.
- $strPattern = str_replace(array('*', '?'), array('%', '_'), $strPattern);
- return $this->connection->createQueryBuilder()
+ $strPattern = str_replace(['*', '?'], ['%', '_'], $strPattern);
+
+ $statement = $this->connection
+ ->createQueryBuilder()
->select('t.id')
->from($this->getMetaModel()->getTableName(), 't')
->where('t.' . $this->getColName() . ' LIKE :pattern')
->setParameter('pattern', $strPattern)
- ->execute()
- ->fetchAll(\PDO::FETCH_COLUMN, 'id');
+ ->executeQuery();
+
+ // Return value list as list, parent function wants a list so we make a cast.
+ return array_map(static fn (mixed $value) => (string) $value, $statement->fetchFirstColumn());
}
/**
@@ -240,9 +276,15 @@ public function searchFor($strPattern)
* Deriving classes SHOULD override this function.
*
* @return string 'blob NULL'
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function getSQLDataType()
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+ }
+
return 'blob NULL';
}
@@ -250,10 +292,23 @@ public function getSQLDataType()
* Create auxiliary data like a column in the MetaModel table or references in another table etc.
*
* @return void
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function destroyAUX()
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
+
+ /** @psalm-suppress DeprecatedMethod */
parent::destroyAUX();
+
+ /** @psalm-suppress DeprecatedMethod */
$this->deleteColumn();
}
@@ -261,10 +316,25 @@ public function destroyAUX()
* Delete all auxiliary data like a column in the MetaModel table or references in another table etc.
*
* @return void
+ *
+ * @throws Exception
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function initializeAUX()
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
+
+ /** @psalm-suppress DeprecatedMethod */
parent::initializeAUX();
+
+ /** @psalm-suppress DeprecatedMethod */
$this->createColumn();
}
@@ -274,10 +344,23 @@ public function initializeAUX()
* You have to override this function in field types, when you want to have multi column structure etc.
*
* @return void
+ *
+ * @throws Exception
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function createColumn()
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
+
if ($this->getColName()) {
+ /** @psalm-suppress DeprecatedMethod */
$this->tableManipulator->createColumn(
$this->getMetaModel()->getTableName(),
$this->getColName(),
@@ -290,10 +373,22 @@ public function createColumn()
* Removes the underlying database structure for this field.
*
* @return void
+ *
+ * @throws Exception
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function deleteColumn()
{
- $schemaManager = $this->connection->getSchemaManager();
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
+
+ $schemaManager = $this->connection->createSchemaManager();
$columns = $schemaManager->listTableColumns($this->getMetaModel()->getTableName());
// Try to delete the column. If it does not exist as we can assume it has been deleted already then.
@@ -308,15 +403,28 @@ public function deleteColumn()
* @param string $strNewColumnName The new column name.
*
* @return void
+ *
+ * @throws Exception
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function renameColumn($strNewColumnName)
{
+ if ($this->isManagedAttribute($this->get('type'))) {
+ $this->triggerDeprecationShouldNotCallManaged(static::class, __METHOD__);
+
+ return;
+ }
+
+ $this->triggerDeprecationIsUnmanagedAttribute(static::class, __METHOD__);
+
$this->tableManipulator->checkColumnName($strNewColumnName);
- $schemaManager = $this->connection->getSchemaManager();
+ $schemaManager = $this->connection->createSchemaManager();
$columns = $schemaManager->listTableIndexes($this->getMetaModel()->getTableName());
if ($this->getColName() && isset($columns[$this->getColName()])) {
+ /** @psalm-suppress DeprecatedMethod */
$this->tableManipulator->renameColumn(
$this->getMetaModel()->getTableName(),
$this->getColName(),
@@ -326,6 +434,7 @@ public function renameColumn($strNewColumnName)
} else {
$strBackupColName = $this->getColName();
$this->set('colname', $strNewColumnName);
+ /** @psalm-suppress DeprecatedMethod */
$this->createColumn();
$this->set('colname', $strBackupColName);
}
@@ -348,10 +457,14 @@ public function unserializeData($value)
*
* @param mixed $value The input value.
*
- * @return string
+ * @return string|null
*/
public function serializeData($value)
{
- return $value;
+ if (empty($value)) {
+ return null;
+ }
+
+ return (string) $value;
}
}
diff --git a/src/Attribute/Events/CollectMetaModelAttributeInformationEvent.php b/src/Attribute/Events/CollectMetaModelAttributeInformationEvent.php
index f43f56de6..ca3eab987 100644
--- a/src/Attribute/Events/CollectMetaModelAttributeInformationEvent.php
+++ b/src/Attribute/Events/CollectMetaModelAttributeInformationEvent.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2022 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2022 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -21,7 +22,7 @@
namespace MetaModels\Attribute\Events;
use MetaModels\IMetaModel;
-use Symfony\Component\EventDispatcher\Event;
+use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is triggered for every metamodel when the attribute information for the MetaModel shall be retrieved.
@@ -33,7 +34,7 @@ class CollectMetaModelAttributeInformationEvent extends Event
/**
* The event name.
*/
- const NAME = 'metamodels.metamodel.collect-metamodel-attribute-information';
+ public const NAME = 'metamodels.metamodel.collect-metamodel-attribute-information';
/**
* The MetaModel instance being created.
diff --git a/src/Attribute/Events/CreateAttributeEvent.php b/src/Attribute/Events/CreateAttributeEvent.php
index cbaf752f7..6ecd2932d 100644
--- a/src/Attribute/Events/CreateAttributeEvent.php
+++ b/src/Attribute/Events/CreateAttributeEvent.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -22,7 +23,7 @@
use MetaModels\Attribute\IAttribute;
use MetaModels\IMetaModel;
-use Symfony\Component\EventDispatcher\Event;
+use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is triggered for every attribute when the factory wants to create an instance.
@@ -32,7 +33,7 @@ class CreateAttributeEvent extends Event
/**
* The event name.
*/
- const NAME = 'metamodels.attribute.create';
+ public const NAME = 'metamodels.attribute.create';
/**
* The attribute information.
@@ -51,7 +52,7 @@ class CreateAttributeEvent extends Event
/**
* The attribute instance.
*
- * @var IAttribute
+ * @var IAttribute|null
*/
protected $attribute;
@@ -59,7 +60,6 @@ class CreateAttributeEvent extends Event
* Create a new instance.
*
* @param array $attributeInformation The attribute information array.
- *
* @param IMetaModel $metaModel The MetaModel instance for which the attribute shall get created for.
*/
public function __construct($attributeInformation, $metaModel)
@@ -91,7 +91,7 @@ public function getMetaModel()
/**
* Retrieve the attribute instance.
*
- * @return IAttribute
+ * @return IAttribute|null
*/
public function getAttribute()
{
diff --git a/src/Attribute/Events/CreateAttributeFactoryEvent.php b/src/Attribute/Events/CreateAttributeFactoryEvent.php
index 12fec04bf..22146d7f8 100644
--- a/src/Attribute/Events/CreateAttributeFactoryEvent.php
+++ b/src/Attribute/Events/CreateAttributeFactoryEvent.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2022 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2022 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -21,7 +22,7 @@
namespace MetaModels\Attribute\Events;
use MetaModels\Attribute\IAttributeFactory;
-use Symfony\Component\EventDispatcher\Event;
+use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is triggered for every attribute factory instance that is created.
diff --git a/src/Attribute/IAttribute.php b/src/Attribute/IAttribute.php
index 0ef30766d..775362930 100644
--- a/src/Attribute/IAttribute.php
+++ b/src/Attribute/IAttribute.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,8 @@
* @author David Maack
* @author Stefan Heimes
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -35,17 +36,17 @@
interface IAttribute
{
/**
- * Retrieve the human readable name (or title) from the attribute.
+ * Retrieve the human-readable name (or title) from the attribute.
*
* If the MetaModel is translated, the currently active language is used,
* with properly falling back to the defined fallback language.
*
- * @return string the human readable name
+ * @return string the human-readable name
*/
public function getName();
/**
- * Queries the attribute for it's column name within it's MetaModel.
+ * Queries the attribute for its column name within it's MetaModel.
*
* @return string the attributes column name.
*/
@@ -70,13 +71,12 @@ public function get($strKey);
/**
* Override a meta information setting.
*
- * All changes to an attribute via set() are considered to be non persistent and therefore will not update any
+ * All changes to an attribute via set() are considered to be non-persistent and therefore will not update any
* structural information or auxiliary properties that might be needed within the attribute type.
*
* For persistent updates, use {@link IAttribute::handleMetaChange()} instead.
*
* @param string $strKey The meta information name that shall be set.
- *
* @param mixed $varValue The value to set.
*
* @return IAttribute Instance of this attribute, for chaining support.
@@ -94,10 +94,11 @@ public function set($strKey, $varValue);
* will then keep the old meta value.
*
* @param string $strMetaName Name of the meta information that shall be updated.
- *
* @param mixed $varNewValue The new value for this meta information.
*
* @return IAttribute The instance of this attribute, to support chaining.
+ *
+ * @deprecated Implement schema manipulators instead - see #1267.
*/
public function handleMetaChange($strMetaName, $varNewValue);
@@ -105,6 +106,8 @@ public function handleMetaChange($strMetaName, $varNewValue);
* Delete all auxiliary data like a column in the MetaModel table or references in another table etc.
*
* @return void
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function destroyAUX();
@@ -112,14 +115,16 @@ public function destroyAUX();
* Create auxiliary data like a column in the MetaModel table or references in another table etc.
*
* @return void
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function initializeAUX();
/**
* Returns all valid settings for the attribute type.
*
- * @return string[] All valid setting names, this re-ensembles the columns in tl_metamodel_attribute
- * this attribute class understands.
+ * @return list All valid setting names, this re-ensembles the columns in tl_metamodel_attribute
+ * this attribute class understands.
*/
public function getAttributeSettingNames();
@@ -130,8 +135,8 @@ public function getAttributeSettingNames();
* Using the optional override parameter, settings known by this attribute can be overridden for the
* generating of the output array.
*
- * @param array $arrOverrides The values to override, for a list of valid parameters, call
- * getAttributeSettingNames().
+ * @param array $arrOverrides The values to override, for a list of valid parameters, call
+ * getAttributeSettingNames().
*
* @return array The DCA array to use as $GLOBALS['TL_DCA']['tablename']['fields']['attribute-name]
*/
@@ -145,7 +150,7 @@ public function getFieldDefinition($arrOverrides = array());
* Due to the fact that it calls getFieldDefinition() internally, the result at least contains
* the sub array 'fields' with the information of this field's settings.
*
- * @param array $arrOverrides See documentation in getFieldDefinition() method.
+ * @param array $arrOverrides See documentation in getFieldDefinition() method.
*
* @return array The DCA array to use as $GLOBALS['tablename']
*
@@ -175,7 +180,6 @@ public function valueToWidget($varValue);
* value.
*
* @param mixed $varValue The value to be transformed.
- *
* @param string $itemId The id of the item the value belongs to.
*
* @return mixed The resulting native value
@@ -185,7 +189,7 @@ public function widgetToValue($varValue, $itemId);
/**
* This method is called to store the data for certain items to the database.
*
- * @param mixed[] $arrValues The values to be stored into database. Mapping is item id=>value.
+ * @param array $arrValues The values to be stored into database. Mapping is item id=>value.
*
* @return void
*/
@@ -206,9 +210,7 @@ public function getDefaultRenderSettings();
* Each attribute class MAY return as many other values in this array with custom keys as it wants.
*
* @param array $arrRowData The (native) row data from the MetaModel table.
- *
* @param string $strOutputFormat The desired output format.
- *
* @param ISimpleRenderSetting|null $objSettings Custom settings to be passed to the renderer.
*
* @return array An array with all the converted data.
@@ -227,11 +229,10 @@ public function getFilterUrlValue($varValue);
/**
* Sorts the given array list by field value in the given direction.
*
- * @param string[] $idList A list of Ids from the MetaModel table.
- *
- * @param string $strDirection The direction for sorting. either 'ASC' or 'DESC', as in plain SQL.
+ * @param list $idList A list of Ids from the MetaModel table.
+ * @param string $strDirection The direction for sorting. either 'ASC' or 'DESC', as in plain SQL.
*
- * @return string[] The sorted array.
+ * @return list The sorted array.
*/
public function sortIds($idList, $strDirection);
@@ -247,12 +248,10 @@ public function sortIds($idList, $strDirection);
* This is only relevant, when using "null" as id list for attributes that have pre configured
* values like select lists and tags i.e.
*
- * @param string[]|null $idList The ids of items that the values shall be fetched from
- * (If empty or null, all items).
- *
- * @param bool $usedOnly Determines if only "used" values shall be returned.
- *
- * @param array|null $arrCount Array for the counted values.
+ * @param list|null $idList The ids of items that the values shall be fetched from
+ * (If empty or null, all items).
+ * @param bool $usedOnly Determines if only "used" values shall be returned.
+ * @param array|null $arrCount Array for the counted values.
*
* @return array All options matching the given conditions as name => value.
*/
@@ -265,7 +264,7 @@ public function getFilterOptions($idList, $usedOnly, &$arrCount = null);
*
* @param string $strPattern The text to search for. This may contain wildcards.
*
- * @return string[]|null The list of item ids of all items matching the condition or null if all match.
+ * @return list The list of item ids of all items matching the condition or null if all match.
*/
public function searchFor($strPattern);
@@ -273,10 +272,9 @@ public function searchFor($strPattern);
* Filter all values greater than the passed value.
*
* @param mixed $varValue The value to use as lower end.
- *
* @param bool $blnInclusive If true, the passed value will be included, if false, it will be excluded.
*
- * @return string[]|null The list of item ids of all items matching the condition or null if all match.
+ * @return list|null The list of item ids of all items matching the condition or null if all match.
*/
public function filterGreaterThan($varValue, $blnInclusive = false);
@@ -284,10 +282,9 @@ public function filterGreaterThan($varValue, $blnInclusive = false);
* Filter all values less than the passed value.
*
* @param mixed $varValue The value to use as upper end.
- *
* @param bool $blnInclusive If true, the passed value will be included, if false, it will be excluded.
*
- * @return string[]|null The list of item ids of all items matching the condition or null if all match.
+ * @return list|null The list of item ids of all items matching the condition or null if all match.
*/
public function filterLessThan($varValue, $blnInclusive = false);
@@ -296,7 +293,7 @@ public function filterLessThan($varValue, $blnInclusive = false);
*
* @param mixed $varValue The value to use as upper end.
*
- * @return string[]|null The list of item ids of all items matching the condition or null if all match.
+ * @return list|null The list of item ids of all items matching the condition or null if all match.
*/
public function filterNotEqual($varValue);
diff --git a/src/Attribute/IAttributeFactory.php b/src/Attribute/IAttributeFactory.php
index 8118c62fa..8b272abe7 100644
--- a/src/Attribute/IAttributeFactory.php
+++ b/src/Attribute/IAttributeFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -32,36 +33,35 @@ interface IAttributeFactory extends IServiceContainerAware
/**
* Flag for filtering translated attributes.
*/
- const FLAG_INCLUDE_TRANSLATED = 1;
+ public const FLAG_INCLUDE_TRANSLATED = 1;
/**
* Flag for translated attributes.
*/
- const FLAG_INCLUDE_SIMPLE = 2;
+ public const FLAG_INCLUDE_SIMPLE = 2;
/**
* Flag for complex attributes.
*/
- const FLAG_INCLUDE_COMPLEX = 4;
+ public const FLAG_INCLUDE_COMPLEX = 4;
/**
* Flag for retrieving all attribute types.
*/
- const FLAG_ALL = 7;
+ public const FLAG_ALL = 7;
/**
* Flag for filtering untranslated attributes.
*
* NOTE: When using this flag, translated complex and translated simple types will also get returned.
*/
- const FLAG_ALL_UNTRANSLATED = 6;
+ public const FLAG_ALL_UNTRANSLATED = 6;
/**
* Create an attribute instance from an information array.
*
- * @param array $information The attribute information.
- *
- * @param IMetaModel $metaModel The MetaModel instance for which the attribute shall be created.
+ * @param array $information The attribute information.
+ * @param IMetaModel $metaModel The MetaModel instance for which the attribute shall be created.
*
* @return IAttribute|null
*/
@@ -81,7 +81,7 @@ public function addTypeFactory(IAttributeTypeFactory $typeFactory);
*
* @param string $typeFactory The name of the type factory to retrieve.
*
- * @return IAttributeTypeFactory
+ * @return IAttributeTypeFactory|null
*/
public function getTypeFactory($typeFactory);
@@ -99,9 +99,9 @@ public function attributeTypeMatchesFlags($factory, $flags);
/**
* Retrieve the type names registered in the factory.
*
- * @param bool|int $flags The flags for retrieval. See the interface constants for the different values.
+ * @param false|int $flags The flags for retrieval. See the interface constants for the different values.
*
- * @return string[]
+ * @return list
*/
public function getTypeNames($flags = false);
diff --git a/src/Attribute/IComplex.php b/src/Attribute/IComplex.php
index 1d173dc2b..2b5603d49 100644
--- a/src/Attribute/IComplex.php
+++ b/src/Attribute/IComplex.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,8 @@
* @author Christian Schiffler
* @author David Maack
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -31,17 +32,17 @@ interface IComplex extends IAttribute
/**
* This method is called to retrieve the data for certain items from the database.
*
- * @param string[] $arrIds The ids of the items to retrieve.
+ * @param list $arrIds The ids of the items to retrieve.
*
- * @return mixed[] The nature of the resulting array is a mapping from id => "native data" where
- * the definition of "native data" is only of relevance to the given item.
+ * @return array The nature of the resulting array is a mapping from id => "native data" where
+ * the definition of "native data" is only of relevance to the given item.
*/
public function getDataFor($arrIds);
/**
* Remove values for items.
*
- * @param string[] $arrIds The ids of the items to retrieve.
+ * @param list $arrIds The ids of the items to retrieve.
*
* @return void
*/
diff --git a/src/Attribute/ISimple.php b/src/Attribute/ISimple.php
index 9724950fe..4614acfb3 100644
--- a/src/Attribute/ISimple.php
+++ b/src/Attribute/ISimple.php
@@ -32,6 +32,8 @@ interface ISimple extends IAttribute
* Returns the SQL primitive type declaration in MySQL notation. i.e. "text NULL".
*
* @return string
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function getSQLDataType();
@@ -39,6 +41,8 @@ public function getSQLDataType();
* Creates the underlying database structure for this attribute.
*
* @return void
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function createColumn();
@@ -46,6 +50,8 @@ public function createColumn();
* Removes the underlying database structure for this attribute.
*
* @return void
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function deleteColumn();
@@ -55,6 +61,8 @@ public function deleteColumn();
* @param string $strNewColumnName The new column name for the attribute.
*
* @return void
+ *
+ * @deprecated Implement schema generators instead - see #1267.
*/
public function renameColumn($strNewColumnName);
@@ -72,7 +80,7 @@ public function unserializeData($value);
*
* @param mixed $value The input value.
*
- * @return string
+ * @return string|null
*/
public function serializeData($value);
}
diff --git a/src/Attribute/ITranslated.php b/src/Attribute/ITranslated.php
index 84aa3a415..144f2955f 100644
--- a/src/Attribute/ITranslated.php
+++ b/src/Attribute/ITranslated.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -39,21 +40,19 @@ public function getMetaModel();
/**
* Search matches for the given expression.
*
- * @param string $strPattern The text to search for. This may contain wildcards.
+ * @param string $strPattern The text to search for. This may contain wildcards.
+ * @param list $arrLanguages Array of valid language codes that shall be searched.
+ * (optional) If empty, all languages will be taken into account.
*
- * @param array $arrLanguages Array of valid language codes that shall be searched. (optional)
- * If empty, all languages will be taken into account.
- *
- * @return string[] the ids of matching items.
+ * @return list the ids of matching items.
*/
- public function searchForInLanguages($strPattern, $arrLanguages = array());
+ public function searchForInLanguages($strPattern, $arrLanguages = []);
/**
* Set a value for an item in a certain language.
*
- * @param mixed[] $arrValues The values to be set in id => value layout.
- *
- * @param string $strLangCode The language code for which the data shall be retrieved.
+ * @param array> $arrValues The values to be set in id => value layout.
+ * @param string $strLangCode The language code for which the data shall be retrieved.
*
* @return void
*/
@@ -62,20 +61,18 @@ public function setTranslatedDataFor($arrValues, $strLangCode);
/**
* Get values for the given items in a certain language.
*
- * @param string[] $arrIds The ids for which values shall be retrieved.
+ * @param list $arrIds The ids for which values shall be retrieved.
+ * @param string $strLangCode The language code for which the data shall be retrieved.
*
- * @param string $strLangCode The language code for which the data shall be retrieved.
- *
- * @return mixed[] the values.
+ * @return array> the values.
*/
public function getTranslatedDataFor($arrIds, $strLangCode);
/**
* Remove values for items in a certain language.
*
- * @param string[] $arrIds The ids for which values shall be removed.
- *
- * @param string $strLangCode The language code for which the data shall be removed.
+ * @param list $arrIds The ids for which values shall be removed.
+ * @param string $strLangCode The language code for which the data shall be removed.
*
* @return void
*/
diff --git a/src/Attribute/ManagedAttributeTrait.php b/src/Attribute/ManagedAttributeTrait.php
new file mode 100644
index 000000000..235dbd107
--- /dev/null
+++ b/src/Attribute/ManagedAttributeTrait.php
@@ -0,0 +1,58 @@
+
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+namespace MetaModels\Attribute;
+
+use Contao\System;
+
+trait ManagedAttributeTrait
+{
+ private function isManagedAttribute(string $type): bool
+ {
+ $container = System::getContainer();
+ $parameter = [];
+ if ($container->hasParameter('metamodels.managed-schema-type-names')) {
+ $parameter = $container->getParameter('metamodels.managed-schema-type-names');
+ assert(\is_array($parameter));
+ }
+ return \in_array($type, $parameter, true);
+ }
+
+ private function triggerDeprecationIsUnmanagedAttribute(string $class, string $method): void
+ {
+ // @codingStandardsIgnoreStart
+ @trigger_error(
+ 'Class "' . $class . '" should be changed to a managed attribute and skip calling method "' . $method .
+ '".',
+ E_USER_DEPRECATED
+ );
+ // @codingStandardsIgnoreEnd
+ }
+
+ private function triggerDeprecationShouldNotCallManaged(string $class, string $method): void
+ {
+ // @codingStandardsIgnoreStart
+ @trigger_error(
+ 'Class "' . $class . '" is a managed attribute you should not call "' . $method . '".',
+ E_USER_DEPRECATED
+ );
+ // @codingStandardsIgnoreEnd
+ }
+}
diff --git a/src/Attribute/TranslatedReference.php b/src/Attribute/TranslatedReference.php
index 3cf65f342..900b999e1 100644
--- a/src/Attribute/TranslatedReference.php
+++ b/src/Attribute/TranslatedReference.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -19,7 +19,7 @@
* @author Sven Baumann
* @author David Molineus
* @author Andreas Fischer
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -27,14 +27,16 @@
namespace MetaModels\Attribute;
use Contao\System;
+use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
-use MetaModels\Filter\Rules\SimpleQuery;
use MetaModels\IMetaModel;
use MetaModels\ITranslatedMetaModel;
/**
* This is the MetaModelAttribute class for handling translated attributes that reference another table.
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
abstract class TranslatedReference extends BaseComplex implements ITranslated
{
@@ -50,11 +52,11 @@ abstract class TranslatedReference extends BaseComplex implements ITranslated
*
* Note that you should not use this directly but use the factory classes to instantiate attributes.
*
- * @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
- * @param array $arrData The information array, for attribute information, refer to documentation of
- * table tl_metamodel_attribute and documentation of the certain attribute classes
- * for information what values are understood.
- * @param Connection $connection Database connection.
+ * @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
+ * @param array $arrData The information array, for attribute information, refer to documentation of
+ * table tl_metamodel_attribute and documentation of the certain attribute
+ * classes for information what values are understood.
+ * @param Connection|null $connection Database connection.
*/
public function __construct(IMetaModel $objMetaModel, $arrData = [], Connection $connection = null)
{
@@ -68,8 +70,8 @@ public function __construct(IMetaModel $objMetaModel, $arrData = [], Connection
);
// @codingStandardsIgnoreEnd
$connection = System::getContainer()->get('database_connection');
+ assert($connection instanceof Connection);
}
-
$this->connection = $connection;
}
@@ -83,54 +85,52 @@ abstract protected function getValueTable();
/**
* Build a where clause for the given id(s) and language code.
*
- * @param QueryBuilder $queryBuilder The query builder for the query being build.
- * @param string[]|string|null $mixIds One, none or many ids to use.
- * @param string|string[] $mixLangCode The language code/s to use, optional.
+ * @param QueryBuilder $queryBuilder The query builder for the query being build.
+ * @param list|string|null $mixIds One, none or many ids to use.
+ * @param list $mixLangCode The language code/s to use, optional.
*
* @return void
*/
- private function buildWhere(QueryBuilder $queryBuilder, $mixIds, $mixLangCode = '')
- {
- $alias = '';
- if (null !== $firstFromAlias = $queryBuilder->getQueryPart('from')[0]['alias']) {
- $alias = $firstFromAlias . '.';
- }
-
+ private function buildWhere(
+ QueryBuilder $queryBuilder,
+ array|string|null $mixIds,
+ array $mixLangCode,
+ string $alias
+ ): void {
$queryBuilder
- ->andWhere($alias . 'att_id = :att_id')
+ ->andWhere($alias . '.att_id = :att_id')
->setParameter('att_id', $this->get('id'));
- if (!empty($mixIds)) {
- if (is_array($mixIds)) {
- $queryBuilder
- ->andWhere($alias . 'item_id IN (:item_ids)')
- ->setParameter('item_ids', $mixIds, Connection::PARAM_STR_ARRAY);
+ if (null !== $mixIds) {
+ if (\is_array($mixIds)) {
+ if ([] === $mixIds) {
+ $queryBuilder
+ ->andWhere('1=0');
+ } else {
+ $queryBuilder
+ ->andWhere($alias . '.item_id IN (:item_ids)')
+ ->setParameter('item_ids', $mixIds, ArrayParameterType::STRING);
+ }
} else {
$queryBuilder
- ->andWhere($alias . 'item_id = :item_id')
+ ->andWhere($alias . '.item_id = :item_id')
->setParameter('item_id', $mixIds);
}
}
- if (!empty($mixLangCode)) {
- if (is_array($mixLangCode)) {
- $queryBuilder
- ->andWhere($alias . 'langcode IN (:langcode)')
- ->setParameter('langcode', $mixLangCode, Connection::PARAM_STR_ARRAY);
- } else {
- $queryBuilder
- ->andWhere($alias . 'langcode = :langcode')
- ->setParameter('langcode', $mixLangCode);
- }
+ if ([] !== $mixLangCode) {
+ $queryBuilder
+ ->andWhere($alias . '.langcode IN (:langcode)')
+ ->setParameter('langcode', $mixLangCode, ArrayParameterType::STRING);
}
}
/**
* Retrieve the values to be used in the INSERT or UPDATE SQL for the given parameters.
*
- * @param array $arrValue The native value of the attribute.
- * @param int $intId The id of the item to be saved.
- * @param string $strLangCode The language code of the language the value is in.
+ * @param array{value: mixed, ...} $arrValue The native value of the attribute.
+ * @param string $intId The id of the item to be saved.
+ * @param string $strLangCode The language code of the language the value is in.
*
* @return array
*
@@ -138,18 +138,22 @@ private function buildWhere(QueryBuilder $queryBuilder, $mixIds, $mixLangCode =
*/
protected function getSetValues($arrValue, $intId, $strLangCode)
{
- if (($arrValue !== null) && !is_array($arrValue)) {
- throw new \InvalidArgumentException(sprintf('Invalid value provided: %s', var_export($arrValue, true)));
+ /**
+ * @psalm-suppress DocblockTypeContradiction
+ * @psalm-suppress RedundantConditionGivenDocblockType
+ * Remove when we have strict type hints in the method signature.
+ */
+ if (($arrValue === null) || !\is_array($arrValue)) {
+ throw new \InvalidArgumentException(\sprintf('Invalid value provided: %s', \var_export($arrValue, true)));
}
- return array
- (
- 'tstamp' => time(),
- 'value' => (string) $arrValue['value'],
- 'att_id' => $this->get('id'),
+ return [
+ 'tstamp' => \time(),
+ 'value' => (string) $arrValue['value'],
+ 'att_id' => $this->get('id'),
'langcode' => $strLangCode,
- 'item_id' => $intId,
- );
+ 'item_id' => $intId,
+ ];
}
/**
@@ -161,10 +165,10 @@ protected function getSetValues($arrValue, $intId, $strLangCode)
*/
protected function getOptionizer()
{
- return array(
- 'key' => 'value',
+ return [
+ 'key' => 'value',
'value' => 'value'
- );
+ ];
}
@@ -173,7 +177,7 @@ protected function getOptionizer()
*/
public function valueToWidget($varValue)
{
- return $varValue['value'];
+ return $varValue['value'] ?? null;
}
/**
@@ -183,12 +187,11 @@ public function valueToWidget($varValue)
*/
public function widgetToValue($varValue, $itemId)
{
- return array
- (
- 'tstamp' => time(),
+ return [
+ 'tstamp' => \time(),
'value' => $varValue,
'att_id' => $this->get('id'),
- );
+ ];
}
/**
@@ -196,35 +199,38 @@ public function widgetToValue($varValue, $itemId)
*/
public function getDataFor($arrIds)
{
- $strActiveLanguage = $this->getActiveLanguage();
+ /** @psalm-suppress DeprecatedMethod */
+ $strActiveLanguage = $this->getActiveLanguage();
+ /** @psalm-suppress DeprecatedMethod */
$strFallbackLanguage = $this->getFallbackLanguage();
$arrReturn = $this->getTranslatedDataFor($arrIds, $strActiveLanguage);
// Second round, fetch fallback languages if not all items could be resolved.
- if ((count($arrReturn) < count($arrIds)) && ($strActiveLanguage != $strFallbackLanguage)) {
- $arrFallbackIds = array();
+ if (($strActiveLanguage !== $strFallbackLanguage) && (\count($arrReturn) < \count($arrIds))) {
+ $arrFallbackIds = [];
foreach ($arrIds as $intId) {
- if (empty($arrReturn[$intId])) {
+ if (!\array_key_exists($intId, $arrReturn)) {
$arrFallbackIds[] = $intId;
}
}
if ($arrFallbackIds) {
- $arrFallbackData = $this->getTranslatedDataFor($arrFallbackIds, $strFallbackLanguage);
+ $arrFallbackData = $this->getTranslatedDataFor($arrFallbackIds, $strFallbackLanguage ?? '');
// Cannot use array_merge here as it would renumber the keys.
foreach ($arrFallbackData as $intId => $arrValue) {
$arrReturn[$intId] = $arrValue;
}
}
}
+
return $arrReturn;
}
/**
* Determine the available languages.
*
- * @return null|\string[]
+ * @return list
*
* @throws \RuntimeException When an untranslated MetaModel is encountered.
*/
@@ -235,12 +241,14 @@ private function determineLanguages()
return $metaModel->getLanguages();
}
+ /** @psalm-suppress DeprecatedMethod */
$languages = $this->getMetaModel()->getAvailableLanguages();
if ($languages === null) {
throw new \RuntimeException(
'MetaModel ' . $this->getMetaModel()->getName() . ' does not seem to be translated.'
);
}
+
return $languages;
}
@@ -269,17 +277,20 @@ public function unsetDataFor($arrIds)
*/
public function searchFor($strPattern)
{
- return $this->searchForInLanguages($strPattern, array($this->getActiveLanguage()));
+ return $this->searchForInLanguages($strPattern, [$this->getActiveLanguage()]);
}
/**
* {@inheritDoc}
*/
- public function searchForInLanguages($strPattern, $arrLanguages = array())
+ public function searchForInLanguages($strPattern, $arrLanguages = [])
{
- $optionizer = $this->getOptionizer();
+ if (empty($optionizer = $this->getOptionizer())) {
+ return [];
+ }
+
$procedure = 't.' . $optionizer['value'] . ' LIKE :pattern';
- $strPattern = str_replace(['*', '?'], ['%', '_'], $strPattern);
+ $strPattern = \str_replace(['*', '?'], ['%', '_'], $strPattern);
$queryBuilder = $this->connection->createQueryBuilder()
->select('DISTINCT t.item_id')
@@ -287,11 +298,12 @@ public function searchForInLanguages($strPattern, $arrLanguages = array())
->andWhere($procedure)
->setParameter('pattern', $strPattern);
- $this->buildWhere($queryBuilder, null, $arrLanguages);
+ $this->buildWhere($queryBuilder, null, $arrLanguages, 't');
- $filterRule = SimpleQuery::createFromQueryBuilder($queryBuilder, 'item_id');
+ $statement = $queryBuilder->executeQuery();
- return $filterRule->getMatchingIds();
+ // Return value list as list, parent function wants a list so we make a cast.
+ return \array_map(static fn (mixed $value) => (string) $value, $statement->fetchFirstColumn());
}
/**
@@ -299,42 +311,33 @@ public function searchForInLanguages($strPattern, $arrLanguages = array())
*/
public function sortIds($idList, $strDirection)
{
- $langSet = sprintf(
- '\'%s\',\'%s\'',
- $this->getActiveLanguage(),
- $this->getFallbackLanguage()
- );
-
- $statement = $this->connection
- ->executeQuery(
- sprintf(
- 'SELECT t1.item_id
- FROM %1$s AS t1
- INNER JOIN %1$s as t3 ON (t1.id = (SELECT
- t2.id
- FROM %1$s AS t2
- WHERE (t2.att_id=%2$s)
- AND langcode IN (%3$s)
- AND (t2.item_id=t1.item_id)
- ORDER BY FIELD(t2.langcode,%3$s)
- LIMIT 1
- ))
- WHERE (t1.item_id IN (?))
- AND (t3.item_id IN (?))
- GROUP BY t1.id
- ORDER BY t1.value %4$s',
- // @codingStandardsIgnoreStart - we want to keep the numbers at the end of the lines below.
- $this->getValueTable(), // 1
- $this->get('id'), // 2
- $langSet, // 3
- $strDirection // 4
- // @codingStandardsIgnoreEnd
- ),
- [$idList, $idList],
- [Connection::PARAM_STR_ARRAY, Connection::PARAM_STR_ARRAY]
- );
-
- return $statement->fetchAll(\PDO::FETCH_COLUMN, 0);
+ $queryBuilder = $this->connection->createQueryBuilder();
+ $expr = $queryBuilder->expr();
+ $queryBuilder
+ ->select('IF(t2.item_id IS NOT NULL, t2.item_id, t1.item_id)')
+ ->from($this->getValueTable(), 't1')
+ ->leftJoin(
+ 't1',
+ $this->getValueTable(),
+ 't2',
+ (string) $expr->and(
+ $expr->eq('t1.att_id', 't2.att_id'),
+ $expr->eq('t1.item_id', 't2.item_id'),
+ $expr->eq('t2.langcode', ':langcode'),
+ )
+ )
+ ->where($expr->eq('t1.att_id', ':att_id'))
+ ->andWhere($expr->in('t1.item_id', ':id_list'))
+ ->andWhere($expr->in('t1.langcode', ':langfallbackcode'))
+ ->setParameter('langcode', $this->getActiveLanguage())
+ ->setParameter('langfallbackcode', $this->getFallbackLanguage())
+ ->setParameter('att_id', $this->get('id'))
+ ->setParameter('id_list', \array_unique($idList), ArrayParameterType::STRING);
+
+ $statement = $queryBuilder->executeQuery();
+
+ // Return value list as list, parent function wants a list so we make a cast.
+ return \array_map(static fn(mixed $value) => (string) $value, $statement->fetchFirstColumn());
}
/**
@@ -346,14 +349,14 @@ public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
->select('t.*')
->from($this->getValueTable(), 't');
- $this->buildWhere($queryBuilder, $idList, $this->getActiveLanguage());
+ $this->buildWhere($queryBuilder, $idList, [$this->getActiveLanguage()], 't');
- $statement = $queryBuilder->execute();
+ $statement = $queryBuilder->executeQuery();
$arrOptionizer = $this->getOptionizer();
$arrReturn = [];
- while ($objValue = $statement->fetch(\PDO::FETCH_OBJ)) {
- $arrReturn[$objValue->{$arrOptionizer['key']}] = $objValue->{$arrOptionizer['value']};
+ while ($objValue = $statement->fetchAssociative()) {
+ $arrReturn[$objValue[$arrOptionizer['key']]] = $objValue[$arrOptionizer['value']];
}
return $arrReturn;
}
@@ -363,64 +366,66 @@ public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
*/
public function setTranslatedDataFor($arrValues, $strLangCode)
{
+ if ('' === $strLangCode) {
+ throw new \InvalidArgumentException('Empty language code provided.');
+ }
// First off determine those to be updated and those to be inserted.
- $arrIds = array_keys($arrValues);
+ $arrIds = \array_keys($arrValues);
$arrExisting = $this->fetchExistingIdsFor($arrIds, $strLangCode);
- $arrNewIds = array_diff($arrIds, $arrExisting);
+ $arrNewIds = \array_diff($arrIds, $arrExisting);
+ $tableAlias = $this->getValueTable();
// Update existing values - delete if empty.
foreach ($arrExisting as $intId) {
$queryBuilder = $this->connection->createQueryBuilder();
- $this->buildWhere($queryBuilder, $intId, $strLangCode);
-
- if ($arrValues[$intId]['value'] != '') {
- $queryBuilder->update($this->getValueTable(), 't');
+ $this->buildWhere($queryBuilder, $intId, [$strLangCode], $tableAlias);
+ $itemValues = $arrValues[$intId] ?? [];
+ if ($this->isValidItemValue($itemValues)) {
+ $queryBuilder->update($this->getValueTable());
- foreach ($this->getSetValues($arrValues[$intId], $intId, $strLangCode) as $name => $value) {
+ foreach ($this->getSetValues($itemValues, $intId, $strLangCode) as $name => $value) {
$queryBuilder
- ->set('t.' . $name, ':' . $name)
+ ->set($tableAlias . '.' . $name, ':' . $name)
->setParameter($name, $value);
}
} else {
$queryBuilder->delete($this->getValueTable());
}
-
- $queryBuilder->execute();
+ $queryBuilder->executeQuery();
}
// Insert the new values.
foreach ($arrNewIds as $intId) {
- if ($arrValues[$intId]['value'] == '') {
+ $itemValues = $arrValues[$intId] ?? [];
+ if (!$this->isValidItemValue($itemValues)) {
continue;
}
- $this->connection->insert(
- $this->getValueTable(),
- $this->getSetValues($arrValues[$intId], $intId, $strLangCode)
- );
+ $this->connection->insert($this->getValueTable(), $this->getSetValues($itemValues, $intId, $strLangCode));
}
}
/**
* Filter the item ids for ids that exist in the database.
*
- * @param array $idList The id list.
- * @param string $langCode The language code.
+ * @param list $idList The id list.
+ * @param string $langCode The language code.
*
* @return string[]
*/
protected function fetchExistingIdsFor($idList, $langCode)
{
+ if ('' === $langCode) {
+ throw new \InvalidArgumentException('Empty language code provided.');
+ }
$queryBuilder = $this
->connection
->createQueryBuilder()
->select('t.item_id')
->from($this->getValueTable(), 't');
- $this->buildWhere($queryBuilder, $idList, $langCode);
-
- $statement = $queryBuilder->execute();
+ $this->buildWhere($queryBuilder, $idList, [$langCode], 't');
- return $statement->fetchAll(\PDO::FETCH_COLUMN);
+ return $queryBuilder->executeQuery()->fetchFirstColumn();
}
/**
@@ -428,18 +433,21 @@ protected function fetchExistingIdsFor($idList, $langCode)
*/
public function getTranslatedDataFor($arrIds, $strLangCode)
{
+ if ('' === $strLangCode) {
+ throw new \InvalidArgumentException('Empty language code provided.');
+ }
$queryBuilder = $this->connection->createQueryBuilder()
->select('t.*')
->from($this->getValueTable(), 't');
- $this->buildWhere($queryBuilder, $arrIds, $strLangCode);
+ $this->buildWhere($queryBuilder, $arrIds, [$strLangCode], 't');
- $statement = $queryBuilder->execute();
+ $statement = $queryBuilder->executeQuery();
$arrReturn = [];
- while ($value = $statement->fetch(\PDO::FETCH_ASSOC)) {
- /** @noinspection PhpUndefinedFieldInspection */
+ while ($value = $statement->fetchAssociative()) {
$arrReturn[$value['item_id']] = $value;
}
+
return $arrReturn;
}
@@ -448,39 +456,57 @@ public function getTranslatedDataFor($arrIds, $strLangCode)
*/
public function unsetValueFor($arrIds, $strLangCode)
{
+ if ('' === $strLangCode) {
+ throw new \InvalidArgumentException('Empty language code provided.');
+ }
$queryBuilder = $this->connection->createQueryBuilder()->delete($this->getValueTable());
- $this->buildWhere($queryBuilder, $arrIds, $strLangCode);
+ $this->buildWhere($queryBuilder, $arrIds, [$strLangCode], $this->getValueTable());
- $queryBuilder->execute();
+ $queryBuilder->executeQuery();
}
/**
* Retrieve the current language of the MetaModel we are attached to.
*
- * @return string
+ * @return non-empty-string
*/
- private function getActiveLanguage()
+ private function getActiveLanguage(): string
{
$metaModel = $this->getMetaModel();
if (!$metaModel instanceof ITranslatedMetaModel) {
- return $metaModel->getActiveLanguage();
+ /** @psalm-suppress DeprecatedMethod */
+ $activeLanguage = $metaModel->getActiveLanguage();
+ assert('' !== $activeLanguage);
+
+ return $activeLanguage;
}
+ $activeLanguage = $metaModel->getLanguage();
+ assert('' !== $activeLanguage);
- return $metaModel->getLanguage();
+ return $activeLanguage;
}
/**
* Retrieve the main language of the MetaModel we are attached to.
*
- * @return string
+ * @return string|null
*/
- private function getFallbackLanguage()
+ private function getFallbackLanguage(): ?string
{
$metaModel = $this->getMetaModel();
if (!$metaModel instanceof ITranslatedMetaModel) {
+ /** @psalm-suppress DeprecatedMethod */
return $metaModel->getFallbackLanguage();
}
return $metaModel->getMainLanguage();
}
+
+ /** @psalm-assert-if-true array{value: non-empty-string, ...} $itemValues */
+ private function isValidItemValue(array $itemValues): bool
+ {
+ return \array_key_exists('value', $itemValues)
+ && \is_string($itemValues['value'])
+ && '' !== $itemValues['value'];
+ }
}
diff --git a/src/BackendIntegration/InputScreen/IInputScreen.php b/src/BackendIntegration/InputScreen/IInputScreen.php
index 43a06f441..921316cb8 100644
--- a/src/BackendIntegration/InputScreen/IInputScreen.php
+++ b/src/BackendIntegration/InputScreen/IInputScreen.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,8 @@
* @author Stefan Heimes
* @author Alexander Menk
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -28,6 +29,8 @@
/**
* This interface describes the abstraction of an input screen.
*
+ * @psalm-type TLegend=array{name: string, visible: bool, properties: list}
+ *
* @deprecated This will get removed.
*/
interface IInputScreen
@@ -42,14 +45,14 @@ public function getId();
/**
* Retrieve all legends.
*
- * @return string[]
+ * @return array
*/
public function getLegends();
/**
* Retrieve the names of all legends.
*
- * @return string[]
+ * @return list
*/
public function getLegendNames();
@@ -58,7 +61,7 @@ public function getLegendNames();
*
* @param string $name The name of the legend.
*
- * @return array
+ * @return TLegend
*/
public function getLegend($name);
@@ -81,7 +84,7 @@ public function getProperty($name);
/**
* Retrieve the names of all contained properties.
*
- * @return string[]
+ * @return list
*/
public function getPropertyNames();
diff --git a/src/BackendIntegration/InputScreen/InputScreen.php b/src/BackendIntegration/InputScreen/InputScreen.php
index 9ccfa0460..3e787a72a 100644
--- a/src/BackendIntegration/InputScreen/InputScreen.php
+++ b/src/BackendIntegration/InputScreen/InputScreen.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -18,7 +18,7 @@
* @author Ingolf Steinhardt
* @author Sven Baumann
* @author Richard Henkenjohann
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -32,20 +32,31 @@
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionChain;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionInterface;
use MetaModels\Events\CreatePropertyConditionEvent;
+use MetaModels\Helper\LocaleUtil;
use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;
use MetaModels\ITranslatedMetaModel;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Implementation of IInputScreen.
*
+ * @psalm-import-type TLegend from IInputScreen
+ *
* @deprecated This class will get removed.
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ *
+ * @psalm-suppress DeprecatedInterface
*/
class InputScreen implements IInputScreen
{
/**
* The service container.
*
+ * @psalm-suppress DeprecatedInterface
+ *
* @var IMetaModelsServiceContainer
*/
protected $container;
@@ -60,57 +71,55 @@ class InputScreen implements IInputScreen
/**
* The legends contained within the input screen.
*
- * @var array
+ * @var array
*/
- protected $legends = array();
+ protected $legends = [];
/**
* The properties contained within the input screen.
*
* @var array
*/
- protected $properties = array();
+ protected $properties = [];
/**
* The conditions.
*
* @var ConditionChainInterface[]
*/
- protected $conditions = array();
+ protected $conditions = [];
/**
* Grouping and sorting information.
*
- * @var IInputScreenGroupingAndSorting[]
+ * @var list
*/
- protected $groupSort = array();
+ protected $groupSort = [];
/**
* Simple map from property setting id to property name.
*
* @var array
*/
- protected $propertyMap = array();
+ protected $propertyMap = [];
/**
* Simple map from property name to property setting id.
*
* @var array
*/
- protected $propertyMap2 = array();
+ protected $propertyMap2 = [];
/**
* Create a new instance.
*
* @param IMetaModelsServiceContainer $container The service container.
- *
* @param array $data The information about the input screen.
- *
* @param array $propertyRows The information about all contained properties.
- *
* @param array $conditions The property condition information.
- *
* @param array $groupSort The grouping and sorting information.
+ *
+ * @psalm-suppress DeprecatedInterface
*/
public function __construct($container, $data, $propertyRows, $conditions, $groupSort)
{
@@ -126,7 +135,6 @@ public function __construct($container, $data, $propertyRows, $conditions, $grou
* Transform a legend information into the property legends.
*
* @param array $legend The legend to transform.
- *
* @param IMetaModel $metaModel The metamodel the legend belongs to.
*
* @return string
@@ -138,20 +146,19 @@ public function __construct($container, $data, $propertyRows, $conditions, $grou
protected function translateLegend($legend, $metaModel)
{
$arrLegend = StringUtil::deserialize($legend['legendtitle']);
- if (is_array($arrLegend)) {
- $strLegend = $this->extractLegendName($arrLegend);
+ if (\is_array($arrLegend)) {
+ $strLegend = $this->extractLegendName($arrLegend, $metaModel);
} else {
- $strLegend = $legend['legendtitle'] ? $legend['legendtitle'] : 'legend';
+ $strLegend = $legend['legendtitle'] ?: 'legend';
}
$legendName = StringUtil::standardize($strLegend);
- $this->legends[$legendName] = array
- (
+ $this->legends[$legendName] = [
'name' => $strLegend,
'visible' => !(isset($legend['legendhide']) && (bool) $legend['legendhide']),
- 'properties' => array()
- );
+ 'properties' => []
+ ];
return $legendName;
}
@@ -170,14 +177,15 @@ protected function translateLegend($legend, $metaModel)
private function extractLegendName(array $legend, IMetaModel $metaModel): string
{
// Current backend language.
- $language = \str_replace('-', '_', $GLOBALS['TL_LANGUAGE']);
+ // @deprecated usage of TL_LANGUAGE - remove for Contao 5.0.
+ $language = LocaleUtil::formatAsLocale($GLOBALS['TL_LANGUAGE']);
if (null !== ($result = $legend[$language] ?? null)) {
return $result;
}
// Is it a regional locale?
- if (false !== strpos($language, '_')) {
- $chunks = explode('_', $language);
- $language = array_shift($chunks);
+ if (\str_contains($language, '_')) {
+ $chunks = \explode('_', $language);
+ $language = \array_shift($chunks);
unset($chunks);
if (null !== ($result = $legend[$language] ?? null)) {
return $result;
@@ -186,33 +194,32 @@ private function extractLegendName(array $legend, IMetaModel $metaModel): string
// Try fallback language then.
if ($metaModel instanceof ITranslatedMetaModel) {
- if (null !== ($result = $legend[$metaModel->getMainLanguage()] ?? null)) {
+ if (null !== ($result = ($legend[$metaModel->getMainLanguage()] ?? null))) {
return $result;
}
} else {
- if (null !== ($result = $legend[$metaModel->getFallbackLanguage()] ?? null)) {
+ /** @psalm-suppress DeprecatedMethod */
+ if (null !== ($result = ($legend[(string) $metaModel->getFallbackLanguage()] ?? null))) {
return $result;
}
}
// Last resort, simply "legend". See issue #926.
- return 'legend' . (count($this->legends) + 1);
+ return 'legend' . (\count($this->legends) + 1);
}
/**
* Translate a property.
*
* @param array $property The property information to transform.
- *
* @param IMetaModel $metaModel The MetaModel the property belongs to.
- *
* @param string $legend The legend the property belongs to.
*
* @return bool
*/
protected function translateProperty($property, $metaModel, $legend)
{
- $attribute = $metaModel->getAttributeById($property['attr_id']);
+ $attribute = $metaModel->getAttributeById((int) $property['attr_id']);
// Dead meat.
if (!$attribute) {
@@ -223,10 +230,9 @@ protected function translateProperty($property, $metaModel, $legend)
$this->legends[$legend]['properties'][] = $propName;
- $this->properties[$propName] = array
- (
- 'info' => $attribute->getFieldDefinition($property),
- );
+ $this->properties[$propName] = [
+ 'info' => $attribute->getFieldDefinition($property),
+ ];
return true;
}
@@ -267,19 +273,19 @@ protected function translateRows($rows)
{
$metaModel = $this->getMetaModel();
$activeLegend = $this->translateLegend(
- array('legendtitle' => $metaModel->getName(), 'legendhide' => false),
+ ['legendtitle' => $metaModel->getName(), 'legendhide' => false],
$metaModel
);
$activeLegendId = null;
// First pass, fetch all attribute names.
- $columnNames = array();
+ $columnNames = [];
foreach ($rows as $row) {
if ($row['dcatype'] != 'attribute') {
continue;
}
- $attribute = $metaModel->getAttributeById($row['attr_id']);
+ $attribute = $metaModel->getAttributeById((int) $row['attr_id']);
if ($attribute) {
$columnNames[$row['id']] = $attribute->getColName();
}
@@ -299,7 +305,7 @@ protected function translateRows($rows)
case 'attribute':
$exists = $this->translateProperty($row, $metaModel, $activeLegend);
- if ($exists && $activeLegendId) {
+ if ($exists && null !== $activeLegendId) {
$this->applyLegendConditions($row['id'], $activeLegendId);
}
@@ -323,19 +329,25 @@ protected function translateRows($rows)
protected function transformCondition($condition)
{
$dispatcher = System::getContainer()->get('event_dispatcher');
- $event = new CreatePropertyConditionEvent($condition, $this->getMetaModel());
+ assert($dispatcher instanceof EventDispatcherInterface);
- /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
+ /** @psalm-suppress DeprecatedClass */
+ $event = new CreatePropertyConditionEvent($condition, $this->getMetaModel());
+
+ /**
+ * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
+ * @psalm-suppress DeprecatedClass
+ */
$dispatcher->dispatch($event, CreatePropertyConditionEvent::NAME);
- if ($event->getInstance() === null) {
- throw new \RuntimeException(sprintf(
+ if (($instance = $event->getInstance()) === null) {
+ throw new \RuntimeException(\sprintf(
'Condition of type %s could not be transformed to an instance.',
$condition['type']
));
}
- return $event->getInstance();
+ return $instance;
}
/**
@@ -348,14 +360,14 @@ protected function transformCondition($condition)
protected function transformConditions($conditions)
{
// First pass, sort them into pid.
- $sorted = array();
- $byPid = array();
- foreach ($conditions as $i => $condition) {
- $sorted[$condition['id']] = $conditions[$i];
+ $sorted = [];
+ $byPid = [];
+ foreach ($conditions as $condition) {
+ $sorted[$condition['id']] = $condition;
$byPid[$condition['pid']][] = $condition['id'];
}
- $instances = array();
+ $instances = [];
// Second pass, handle them.
foreach ($sorted as $id => $condition) {
$instances[$id] = $this->transformCondition($condition);
@@ -370,7 +382,7 @@ protected function transformConditions($conditions)
}
$result = $this->conditions[$settingId];
$condition = $instances[$id];
- $parent = ($pid == 0) ? $result : $instances[$pid];
+ $parent = ($pid === 0) ? $result : $instances[$pid];
// have other classes in the future.
if ($parent instanceof ConditionChainInterface) {
@@ -401,7 +413,7 @@ protected function transformGroupSort($rows)
*/
public function getId()
{
- return $this->data['id'];
+ return (int) $this->data['id'];
}
/**
@@ -417,7 +429,7 @@ public function getLegends()
*/
public function getLegendNames()
{
- return array_keys($this->legends);
+ return \array_keys($this->legends);
}
/**
@@ -441,7 +453,7 @@ public function getProperties()
*/
public function getProperty($name)
{
- return isset($this->properties[$name]) ? $this->properties[$name] : null;
+ return $this->properties[$name];
}
/**
@@ -449,9 +461,9 @@ public function getProperty($name)
*/
public function getPropertyNames()
{
- $result = array();
+ $result = [];
foreach ($this->getLegends() as $legend) {
- $result = array_merge($result, $legend['properties']);
+ $result = \array_merge($result, $legend['properties']);
}
return $result;
@@ -463,7 +475,8 @@ public function getPropertyNames()
public function getConditionsFor($name)
{
$property = $this->propertyMap2[$name];
- return isset($this->conditions[$property]) ? $this->conditions[$property] : null;
+
+ return $this->conditions[$property] ?? null;
}
/**
@@ -481,12 +494,14 @@ public function getGroupingAndSorting()
*/
public function getMetaModel()
{
+ /** @psalm-suppress DocblockTypeContradiction */
if (null === $this->data) {
throw new \RuntimeException(
'No input screen data available, did you forget to define the view combinations?'
);
}
+ /** @psalm-suppress DeprecatedMethod */
$factory = $this->container->getFactory();
$metaModel = $factory->getMetaModel($factory->translateIdToMetaModelName($this->data['pid']));
@@ -507,7 +522,7 @@ public function getIcon()
return $this->data['backendicon'];
}
- return null;
+ return '';
}
/**
@@ -515,7 +530,7 @@ public function getIcon()
*/
public function getBackendSection()
{
- return trim($this->data['backendsection']);
+ return trim($this->data['backendsection'] ?? '');
}
/**
@@ -523,7 +538,7 @@ public function getBackendSection()
*/
public function getBackendCaption()
{
- return StringUtil::deserialize($this->data['backendcaption'], true);
+ return StringUtil::deserialize($this->data['backendcaption'] ?? [], true);
}
/**
@@ -531,7 +546,7 @@ public function getBackendCaption()
*/
public function getParentTable()
{
- return $this->data['ptable'];
+ return $this->data['ptable'] ?? null;
}
/**
@@ -553,7 +568,7 @@ public function getRenderMode()
return 'hierarchical';
}
- return $this->data['rendermode'];
+ return $this->data['rendermode'] ?? '';
}
/**
@@ -609,7 +624,7 @@ public function isDeletable()
*/
public function getPanelLayout()
{
- return $this->data['panelLayout'];
+ return $this->data['panelLayout'] ?? '';
}
/**
diff --git a/src/BackendIntegration/InputScreen/InputScreenGroupingAndSorting.php b/src/BackendIntegration/InputScreen/InputScreenGroupingAndSorting.php
index 4de9e4ae3..e73fbee3c 100644
--- a/src/BackendIntegration/InputScreen/InputScreenGroupingAndSorting.php
+++ b/src/BackendIntegration/InputScreen/InputScreenGroupingAndSorting.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2022 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,8 @@
* @author Christian Schiffler
* @author David Molineus
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2022 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -30,6 +31,8 @@ class InputScreenGroupingAndSorting implements IInputScreenGroupingAndSorting
* The parenting input screen.
*
* @var IInputScreen
+ *
+ * @psalm-suppress DeprecatedInterface
*/
protected $inputScreen;
@@ -46,6 +49,8 @@ class InputScreenGroupingAndSorting implements IInputScreenGroupingAndSorting
* @param array $data The information about the input screen.
*
* @param IInputScreen $inputScreen The information about all contained properties.
+ *
+ * @psalm-suppress DeprecatedInterface
*/
public function __construct($data, IInputScreen $inputScreen)
{
@@ -78,7 +83,7 @@ public function getRenderGroupType()
*/
public function getRenderGroupLength()
{
- return (int) $this->data['rendergrouplen'];
+ return (string) $this->data['rendergrouplen'];
}
/**
@@ -88,11 +93,9 @@ public function getRenderGroupAttribute()
{
if (!empty($this->data['rendergroupattr'])) {
$metaModel = $this->getMetaModel();
- if ($metaModel) {
- $attribute = $metaModel->getAttributeById($this->data['rendergroupattr']);
- if ($attribute) {
- return $attribute->getColName();
- }
+ $attribute = $metaModel->getAttributeById((int) $this->data['rendergroupattr']);
+ if ($attribute) {
+ return $attribute->getColName();
}
}
@@ -114,11 +117,9 @@ public function getRenderSortAttribute()
{
if (!empty($this->data['rendersortattr'])) {
$metaModel = $this->getMetaModel();
- if ($metaModel) {
- $attribute = $metaModel->getAttributeById($this->data['rendersortattr']);
- if ($attribute) {
- return $attribute->getColName();
- }
+ $attribute = $metaModel->getAttributeById((int) $this->data['rendersortattr']);
+ if ($attribute) {
+ return $attribute->getColName();
}
}
diff --git a/src/BackendIntegration/Module.php b/src/BackendIntegration/Module.php
index 5e2d55118..8104802a3 100644
--- a/src/BackendIntegration/Module.php
+++ b/src/BackendIntegration/Module.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,20 +15,25 @@
* @author Andreas Isaak
* @author Stefan Heimes
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\BackendIntegration;
+use Contao\Input;
use ContaoCommunityAlliance\DcGeneral\Action;
+use ContaoCommunityAlliance\DcGeneral\Controller\ControllerInterface;
use ContaoCommunityAlliance\DcGeneral\DataContainerInterface;
use ContaoCommunityAlliance\DcGeneral\Contao\Callback\Callbacks;
/**
* Implementation of the MetaModel Backend Module that allowing access to MetaModel configuration etc. Everything below
- * http://..../contao?do=metamodels&.... ends up here.
+ * https://..../contao/metamodels?.... ends up here.
+ *
+ * @deprecated Not in use anymore since 2.3.
*/
class Module
{
@@ -37,7 +42,7 @@ class Module
*
* @var DataContainerInterface
*/
- private $dataContainer;
+ private DataContainerInterface $dataContainer;
/**
* Create a new instance.
@@ -62,15 +67,18 @@ public function generate()
$GLOBALS['TL_CSS'][] = 'bundles/metamodelscore/css/style.css';
$arrModule = $GLOBALS['BE_MOD']['metamodels']['metamodels'];
// Custom action (if key is not defined in config.php the default action will be called).
- if (\Input::get('key') && isset($arrModule[\Input::get('key')])) {
- Callbacks::call($arrModule[\Input::get('key')], $this, $arrModule);
+ if (Input::get('key') && isset($arrModule[Input::get('key')])) {
+ Callbacks::call($arrModule[Input::get('key')], $this, $arrModule);
}
- $act = \Input::get('act');
- if (!strlen($act)) {
+ $act = Input::get('act');
+ if (!\strlen($act)) {
$act = 'showAll';
}
- return $this->dataContainer->getEnvironment()->getController()->handle(new Action($act));
+ $controller = $this->dataContainer->getEnvironment()->getController();
+ assert($controller instanceof ControllerInterface);
+
+ return $controller->handle(new Action($act));
}
}
diff --git a/src/BackendIntegration/PurgeAssets.php b/src/BackendIntegration/PurgeAssets.php
index a3508e4a0..d4683fcc5 100644
--- a/src/BackendIntegration/PurgeAssets.php
+++ b/src/BackendIntegration/PurgeAssets.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2021 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,13 +15,15 @@
* @author Sven Baumann
* @author Christian Schiffler
* @author Richard Henkenjohann
- * @copyright 2012-2021 The MetaModels team.
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\BackendIntegration;
+use Contao\CoreBundle\Monolog\ContaoContext;
+use Contao\Folder;
use Contao\System;
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\System\LogEvent;
@@ -44,13 +46,14 @@ public function purge()
{
foreach ($GLOBALS['TL_PURGE']['folders']['metamodels_assets']['affected'] as $folderName) {
// Purge the folder
- $folder = new \Folder($folderName);
+ $folder = new Folder($folderName);
$folder->purge();
}
$dispatcher = System::getContainer()->get('event_dispatcher');
+ assert($dispatcher instanceof EventDispatcherInterface);
$dispatcher->dispatch(
- new LogEvent('Purged the MetaModels assets', __METHOD__, TL_CRON),
+ new LogEvent('Purged the MetaModels assets', __METHOD__, ContaoContext::CRON),
ContaoEvents::SYSTEM_LOG
);
}
diff --git a/src/BackendIntegration/PurgeCache.php b/src/BackendIntegration/PurgeCache.php
index b6d527236..2ac6adbe0 100644
--- a/src/BackendIntegration/PurgeCache.php
+++ b/src/BackendIntegration/PurgeCache.php
@@ -75,7 +75,7 @@ public function purge()
$this->logger->log(
LogLevel::INFO,
'Purged the MetaModels cache',
- ['contao' => new ContaoContext(__METHOD__, TL_CRON)]
+ ['contao' => new ContaoContext(__METHOD__, ContaoContext::CRON)]
);
}
}
diff --git a/src/BackendIntegration/TemplateList.php b/src/BackendIntegration/TemplateList.php
index d8ec2babf..4484f6e38 100644
--- a/src/BackendIntegration/TemplateList.php
+++ b/src/BackendIntegration/TemplateList.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2020 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -23,6 +23,14 @@
use Doctrine\DBAL\Connection;
use Symfony\Component\Finder\Finder;
+use Symfony\Component\Finder\SplFileInfo;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+use function array_replace_recursive;
+use function array_unique;
+use function implode;
+use function is_dir;
+use function ksort;
/**
* Handy helper class to retrieve a list of templates.
@@ -34,14 +42,14 @@ class TemplateList
*
* @var Connection
*/
- private $database;
+ private Connection $database;
/**
* The resource directories.
*
* @var string[]
*/
- private $resourceDirs;
+ private array $resourceDirs;
/**
* The project root directory.
@@ -50,18 +58,27 @@ class TemplateList
*/
private $rootDir;
+ /**
+ * The translator.
+ *
+ * @var TranslatorInterface
+ */
+ private TranslatorInterface $translator;
+
/**
* Create a new instance.
*
- * @param Connection $database The database connection.
- * @param string[] $resourceDirs The resource directories.
- * @param string $rootDir The root directory.
+ * @param Connection $database The database connection.
+ * @param string[] $resourceDirs The resource directories.
+ * @param string $rootDir The root directory.
+ * @param TranslatorInterface $translator The translator.
*/
- public function __construct(Connection $database, $resourceDirs, $rootDir)
+ public function __construct(Connection $database, $resourceDirs, $rootDir, TranslatorInterface $translator)
{
$this->database = $database;
$this->resourceDirs = $resourceDirs;
$this->rootDir = $rootDir;
+ $this->translator = $translator;
}
/**
@@ -82,12 +99,15 @@ public function getTemplatesForBase($templateBaseName)
$this->fetchTemplatesFromResourceDirectories($templateBaseName)
);
- $templateList = array();
+ $templateList = [];
foreach ($allTemplates as $template => $themeList) {
- $templateList[$template] = sprintf(
- $GLOBALS['TL_LANG']['MSC']['template_in_theme'],
- $template,
- implode(', ', $themeList)
+ $templateList[$template] = $this->translator->trans(
+ 'template_in_theme',
+ [
+ '%template%' => $template,
+ '%themes%' => implode(', ', $themeList)
+ ],
+ 'metamodels_default'
);
}
@@ -106,7 +126,7 @@ public function getTemplatesForBase($templateBaseName)
*/
private function getNoThemeMessage()
{
- return $GLOBALS['TL_LANG']['MSC']['no_theme'];
+ return $this->translator->trans('no_theme', [], 'metamodels_default');
}
/**
@@ -121,7 +141,8 @@ private function fetchRootTemplates($templateBaseName)
return $this->getTemplatesForBaseFrom(
$templateBaseName,
$this->rootDir . '/templates',
- $this->getNoThemeMessage()
+ $this->getNoThemeMessage(),
+ true
);
}
@@ -140,14 +161,14 @@ private function fetchTemplatesFromThemes($templateBaseName)
->createQueryBuilder()
->select('t.id, t.name, t.templates')
->from('tl_theme', 't')
- ->execute()
- ->fetchAll(\PDO::FETCH_ASSOC);
+ ->executeQuery()
+ ->fetchAllAssociative();
// Add all the theme templates folders.
foreach ($themes as $theme) {
$templateDir = $theme['templates'];
$themeName = $theme['name'];
- if ($templateDir != '') {
+ if ($templateDir !== '') {
$allTemplates = array_replace_recursive(
$allTemplates,
$this->getTemplatesForBaseFrom(
@@ -188,15 +209,13 @@ private function fetchTemplatesFromResourceDirectories($templateBaseName)
* Fetch a list of matching templates of the current base within the given folder and the passed theme name.
*
* @param string $base The base for the templates to be retrieved.
- *
* @param string $folder The folder to search in.
- *
* @param string $themeName The name of the theme for the given folder (will get used in the returned description
* text).
*
* @return array
*/
- private function getTemplatesForBaseFrom($base, $folder, $themeName)
+ private function getTemplatesForBaseFrom($base, $folder, $themeName, bool $onlyOneLevel = false)
{
if (!is_dir($folder)) {
return [];
@@ -204,10 +223,13 @@ private function getTemplatesForBaseFrom($base, $folder, $themeName)
$themeName = trim($themeName);
$foundTemplates = Finder::create()->in($folder)->name($base . '*');
+ if ($onlyOneLevel) {
+ $foundTemplates->depth('<1');
+ }
$templates = [];
foreach ($foundTemplates as $template) {
- /** @var \Symfony\Component\Finder\SplFileInfo $template */
+ /** @var SplFileInfo $template */
$templates[$template->getBasename('.' . $template->getExtension())] = [$themeName => $themeName];
}
diff --git a/src/BackendIntegration/ViewCombinations.php b/src/BackendIntegration/ViewCombinations.php
index 7ea5b4d5e..e360c2c07 100644
--- a/src/BackendIntegration/ViewCombinations.php
+++ b/src/BackendIntegration/ViewCombinations.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -21,6 +22,7 @@
namespace MetaModels\BackendIntegration;
use Contao\Environment;
+use Contao\System;
/**
* Class ViewCombinations.
@@ -28,6 +30,8 @@
* Retrieve combinations of view and input screens for the currently logged in user (either frontend or backend).
*
* @deprecated This will get removed.
+ *
+ * @psalm-suppress DeprecatedClass
*/
class ViewCombinations extends \MetaModels\Helper\ViewCombinations
{
@@ -38,30 +42,30 @@ class ViewCombinations extends \MetaModels\Helper\ViewCombinations
*/
protected function authenticateUser()
{
- if (\System::getContainer()->get('cca.dc-general.scope-matcher')->currentScopeIsUnknown()) {
+ $scopeMatcher = System::getContainer()->get('cca.dc-general.scope-matcher');
+ if (null === $scopeMatcher || $scopeMatcher->currentScopeIsUnknown()) {
return false;
}
// Do not execute anything if we are on the login page because no User is logged in.
- if (strpos(Environment::get('script'), 'contao/login') !== false) {
+ if (\str_contains(Environment::get('script'), 'contao/login')) {
return false;
}
// Issue #66 - contao/install.php is not working anymore. Thanks to Stefan Lindecke (@lindesbs).
- if (strpos(Environment::get('request'), 'install') !== false) {
+ if (\str_contains(Environment::get('request'), 'install')) {
return false;
}
- if (strpos(Environment::get('script'), 'system/bin') !== false) {
+ if (\str_contains(Environment::get('script'), 'system/bin')) {
return false;
}
// Bug fix: If the user is not authenticated, contao will redirect to contao/index.php
- // But in this moment the TL_PATH is not defined, so the $this->Environment->request
- // generate a url without replacing the basepath(TL_PATH) with an empty string.
- $authResult = $this->getUser()->authenticate();
-
- return ($authResult === true || $authResult === null) ? true : false;
+ // But at this moment the TL_PATH is not defined, so the $this->Environment->request
+ // generate an url without replacing the basepath(TL_PATH) with an empty string.
+ /** @psalm-suppress DeprecatedMethod */
+ return $this->getUser()->authenticate();
}
/**
@@ -70,13 +74,14 @@ protected function authenticateUser()
protected function getUserGroups()
{
// Try to get the group(s)
- // there might be a NULL in there as BE admins have no groups and user might have one but it is not mandatory.
+ // there might be a NULL in there as BE admins have no groups and user might have one, but it is not mandatory.
// I would prefer a default group for both, fe and be groups.
+ /** @psalm-suppress DeprecatedClass */
$groups = parent::getUserGroups();
/** @noinspection PhpUndefinedFieldInspection */
// Special case in combinations, admins have the implicit group id -1.
- if ($this->getUser()->admin) {
+ if ((bool) $this->getUser()->admin) {
$groups[] = -1;
}
diff --git a/src/CoreBundle/Assets/IconBuilder.php b/src/CoreBundle/Assets/IconBuilder.php
index 1b9dca040..c7795cfc0 100644
--- a/src/CoreBundle/Assets/IconBuilder.php
+++ b/src/CoreBundle/Assets/IconBuilder.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,7 @@
* @author Sven Baumann
* @author Ingolf Steinhardt
* @author David Molineus
- * @copyright 2012-2019 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -24,9 +24,11 @@
use Contao\CoreBundle\Framework\Adapter;
use Contao\CoreBundle\Image\ImageFactoryInterface;
+use Contao\FilesModel;
+use Contao\Image;
use Contao\Validator;
use Symfony\Component\Filesystem\Filesystem;
-use Webmozart\PathUtil\Path;
+use Symfony\Component\Filesystem\Path;
/**
* This class takes care of building icons for the backend.
@@ -38,59 +40,59 @@ class IconBuilder
*
* @var string
*/
- private $rootPath;
+ private string $rootPath;
/**
* The output path for assets.
*
* @var string
*/
- private $outputPath;
+ private string $outputPath;
/**
* The web reachable path for assets.
*
* @var string
*/
- private $webPath;
+ private string $webPath;
/**
* The project web reachable path for assets.
*
* @var string
*/
- private $projectWebPath;
+ private string $projectWebPath;
/**
* Adapter to the Contao\FilesModel class.
*
- * @var \Contao\FilesModel|Adapter
+ * @var Adapter
*/
- private $filesAdapter;
+ private Adapter $filesAdapter;
/**
* The image factory.
*
* @var ImageFactoryInterface
*/
- private $imageFactory;
+ private ImageFactoryInterface $imageFactory;
/**
* The image adapter.
*
- * @var \Contao\Image|Adapter
+ * @var Adapter
*/
- private $image;
+ private Adapter $image;
/**
* Create a new instance.
*
- * @param Adapter $filesAdapter Adapter to the Contao files model class.
+ * @param Adapter $filesAdapter Adapter to the Contao files model class.
* @param ImageFactoryInterface $imageFactory The image factory for resizing images.
* @param string $rootPath The root path of the application.
* @param string $outputPath The output path for assets.
* @param string $webPath The web reachable path for assets.
- * @param Adapter $imageAdapter The image adapter to generate HTML code images.
+ * @param Adapter $imageAdapter The image adapter to generate HTML code images.
* @param string $projectWebPath The project web reachable path for assets.
*/
public function __construct(
@@ -126,10 +128,10 @@ public function __construct(
public function getBackendIcon($icon, $defaultIcon = 'bundles/metamodelscore/images/icons/metamodels.png')
{
$realIcon = $this->convertValueToPath($icon, $defaultIcon);
- $targetPath = $this->outputPath . '/' . basename($realIcon);
+ $targetPath = $this->outputPath . '/' . \basename($realIcon);
if (\file_exists($targetPath)) {
- return $this->webPath . '/' . basename($realIcon);
+ return $this->webPath . '/' . \basename($realIcon);
}
if (!Path::isAbsolute($realIcon)) {
@@ -138,7 +140,7 @@ public function getBackendIcon($icon, $defaultIcon = 'bundles/metamodelscore/ima
$this->imageFactory->create($realIcon, [16, 16, 'center_center'], $targetPath);
- return $this->webPath . '/' . basename($realIcon);
+ return $this->webPath . '/' . \basename($realIcon);
}
/**
@@ -157,6 +159,7 @@ public function getBackendIconImageTag(
$attributes = '',
$defaultIcon = 'bundles/metamodelscore/images/icons/metamodels.png'
) {
+ /** @psalm-suppress InternalMethod - Class Adapter is internal, not the __call() method. Blame Contao. */
return $this->image->getHtml($this->getBackendIcon($icon, $defaultIcon), $alt, $attributes);
}
@@ -171,13 +174,16 @@ public function getBackendIconImageTag(
public function convertValueToPath($varValue, $fallback)
{
if (Validator::isUuid($varValue)) {
+ /** @psalm-suppress InternalMethod - Class Adapter is internal, not the __call() method. Blame Contao. */
$model = $this->filesAdapter->findByPk($varValue);
- if ($model !== null && file_exists($this->rootPath . '/' . $model->path)) {
+ if (($model instanceof FilesModel) && \file_exists($this->rootPath . '/' . $model->path)) {
return $model->path;
}
+
return $fallback;
}
- if (file_exists($varValue)) {
+
+ if (\file_exists($varValue)) {
return $varValue;
}
diff --git a/src/CoreBundle/Command/SchemaValidatorCommand.php b/src/CoreBundle/Command/SchemaValidatorCommand.php
new file mode 100644
index 000000000..7a4583fe3
--- /dev/null
+++ b/src/CoreBundle/Command/SchemaValidatorCommand.php
@@ -0,0 +1,87 @@
+
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+namespace MetaModels\CoreBundle\Command;
+
+use MetaModels\InformationProvider\MetaModelInformationCollector;
+use MetaModels\Schema\SchemaGenerator;
+use MetaModels\Schema\SchemaInformation;
+use MetaModels\Schema\SchemaManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * This validates the schema of the current installation.
+ */
+class SchemaValidatorCommand extends Command
+{
+ /**
+ * The information collector.
+ */
+ private MetaModelInformationCollector $collector;
+
+ /**
+ * The schema generator.
+ */
+ private SchemaGenerator $generator;
+
+ /**
+ * The schema manager.
+ */
+ private SchemaManager $manager;
+
+ public function __construct(
+ MetaModelInformationCollector $collector,
+ SchemaGenerator $generator,
+ SchemaManager $manager
+ ) {
+ $this->collector = $collector;
+ $this->generator = $generator;
+ $this->manager = $manager;
+ parent::__construct('metamodels:schema-update');
+ }
+
+ protected function configure(): void
+ {
+ parent::configure();
+ $this->addOption('force', null, InputOption::VALUE_NONE, 'Perform the update');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->generator->generate($information = new SchemaInformation(), $this->collector->getCollection());
+
+ if ($input->getOption('force')) {
+ $this->manager->preprocess($information);
+ $this->manager->process($information);
+ $this->manager->postprocess($information);
+
+ return 0;
+ }
+
+ foreach ($this->manager->validate($information) as $item) {
+ $output->writeln($item);
+ }
+
+ return 0;
+ }
+}
diff --git a/src/CoreBundle/Contao/Compat/ContaoFactory.php b/src/CoreBundle/Contao/Compat/ContaoFactory.php
index 4629839c4..0a50cb247 100644
--- a/src/CoreBundle/Contao/Compat/ContaoFactory.php
+++ b/src/CoreBundle/Contao/Compat/ContaoFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,8 @@
* @author Christian Schiffler
* @author binron
* @author Stefan Heimes
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -36,13 +37,17 @@ class ContaoFactory
* The Contao framework.
*
* @var ContaoFrameworkInterface
+ *
+ * @psalm-suppress DeprecatedInterface
*/
- private $framework;
+ private ContaoFrameworkInterface $framework;
/**
* Create a new instance.
*
* @param ContaoFrameworkInterface $framework The Contao framework.
+ *
+ * @psalm-suppress DeprecatedInterface
*/
public function __construct(ContaoFrameworkInterface $framework)
{
@@ -64,7 +69,7 @@ public function createInsertTags()
/**
* Create an adapter.
*
- * @param string $className The class name to create an adapter for.
+ * @param class-string $className The class name to create an adapter for.
*
* @return Adapter
*/
diff --git a/src/CoreBundle/Contao/Hooks/AbstractContentElementAndModuleCallback.php b/src/CoreBundle/Contao/Hooks/AbstractContentElementAndModuleCallback.php
index 6d67df856..0ec480529 100644
--- a/src/CoreBundle/Contao/Hooks/AbstractContentElementAndModuleCallback.php
+++ b/src/CoreBundle/Contao/Hooks/AbstractContentElementAndModuleCallback.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,7 @@
* @author Sven Baumann
* @author Ingolf Steinhardt
* @author Marc Reimann
- * @copyright 2012-2020 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -28,14 +28,27 @@
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilderFactoryInterface;
use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Exception;
use MetaModels\BackendIntegration\TemplateList;
use MetaModels\CoreBundle\Assets\IconBuilder;
use MetaModels\Filter\Setting\FilterSettingFactory;
use MetaModels\IFactory;
+use RuntimeException;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+use function asort;
+use function base64_decode;
+use function base64_encode;
+use function in_array;
+use function reset;
+use function sprintf;
+use function trim;
/**
* This class is the abstract base for building the "edit MetaModel" button in the backend.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
abstract class AbstractContentElementAndModuleCallback
{
@@ -51,49 +64,51 @@ abstract class AbstractContentElementAndModuleCallback
*
* @var IconBuilder
*/
- private $iconBuilder;
+ private IconBuilder $iconBuilder;
/**
* The URL builder factory.
*
* @var UrlBuilderFactoryInterface
*/
- private $urlBuilderFactory;
+ private UrlBuilderFactoryInterface $urlBuilderFactory;
/**
* The MetaModel factory.
*
* @var IFactory
*/
- private $factory;
+ private IFactory $factory;
/**
* The filtersetting factory.
*
* @var FilterSettingFactory
*/
- private $filterFactory;
+ private FilterSettingFactory $filterFactory;
/**
* The database connection.
*
* @var Connection
*/
- private $connection;
+ private Connection $connection;
/**
* The template list.
*
* @var TemplateList
*/
- private $templateList;
+ private TemplateList $templateList;
/**
* The request stack.
*
* @var RequestStack
*/
- private $requestStack;
+ private RequestStack $requestStack;
+
+ private TranslatorInterface $translator;
/**
* Create a new instance.
@@ -105,6 +120,7 @@ abstract class AbstractContentElementAndModuleCallback
* @param Connection $connection The database connection.
* @param TemplateList $templateList The template list loader.
* @param RequestStack $requestStack The request stack.
+ * @param TranslatorInterface $translator The translator.
*/
public function __construct(
IconBuilder $iconBuilder,
@@ -113,7 +129,8 @@ public function __construct(
FilterSettingFactory $filterFactory,
Connection $connection,
TemplateList $templateList,
- RequestStack $requestStack
+ RequestStack $requestStack,
+ TranslatorInterface $translator,
) {
$this->iconBuilder = $iconBuilder;
$this->urlBuilderFactory = $urlBuilderFactory;
@@ -122,6 +139,7 @@ public function __construct(
$this->templateList = $templateList;
$this->factory = $factory;
$this->requestStack = $requestStack;
+ $this->translator = $translator;
}
/**
@@ -140,14 +158,17 @@ public function editMetaModelButton(DC_Table $dataContainer)
return '';
}
- $url = $this->urlBuilderFactory->create('contao/main.php?do=metamodels&act=edit')
+ $url = $this->urlBuilderFactory->create('contao/metamodels?act=edit')
->setQueryParameter('id', ModelId::fromValues('tl_metamodel', $dataContainer->value)->getSerialized());
return $this->renderEditButton(
- $GLOBALS['TL_LANG'][static::$tableName]['editmetamodel'][0],
- sprintf(
- StringUtil::specialchars($GLOBALS['TL_LANG'][static::$tableName]['editmetamodel'][1]),
- $dataContainer->value
+ $this->translator->trans('editmetamodel.label', [], static::$tableName),
+ StringUtil::specialchars(
+ $this->translator->trans(
+ 'editmetamodel.description',
+ ['%id%' => $dataContainer->value],
+ static::$tableName
+ )
),
$url
);
@@ -169,17 +190,20 @@ public function editFilterSettingButton(DC_Table $dataContainer)
return '';
}
- $url = $this->urlBuilderFactory->create('contao/main.php?do=metamodels&table=tl_metamodel_filtersetting')
+ $url = $this->urlBuilderFactory->create('contao/metamodels?table=tl_metamodel_filtersetting')
->setQueryParameter(
'pid',
ModelId::fromValues('tl_metamodel_filter', $dataContainer->value)->getSerialized()
);
return $this->renderEditButton(
- $GLOBALS['TL_LANG'][static::$tableName]['editfiltersetting'][0],
- sprintf(
- StringUtil::specialchars($GLOBALS['TL_LANG'][static::$tableName]['editfiltersetting'][1]),
- $dataContainer->value
+ $this->translator->trans('editfiltersetting.label', [], static::$tableName),
+ StringUtil::specialchars(
+ $this->translator->trans(
+ 'editfiltersetting.description',
+ ['%id%' => $dataContainer->value],
+ static::$tableName
+ )
),
$url
);
@@ -201,17 +225,20 @@ public function editRenderSettingButton(DC_Table $dataContainer)
return '';
}
- $url = $this->urlBuilderFactory->create('contao/main.php?do=metamodels&table=tl_metamodel_rendersetting')
+ $url = $this->urlBuilderFactory->create('contao/metamodels?table=tl_metamodel_rendersetting')
->setQueryParameter(
'pid',
ModelId::fromValues('tl_metamodel_rendersettings', $dataContainer->value)->getSerialized()
);
return $this->renderEditButton(
- $GLOBALS['TL_LANG'][static::$tableName]['editrendersetting'][0],
- sprintf(
- StringUtil::specialchars($GLOBALS['TL_LANG'][static::$tableName]['editrendersetting'][1]),
- $dataContainer->value
+ $this->translator->trans('editrendersetting.label', [], static::$tableName),
+ StringUtil::specialchars(
+ $this->translator->trans(
+ 'editrendersetting.description',
+ ['%id%' => $dataContainer->value],
+ static::$tableName
+ ),
),
$url
);
@@ -230,12 +257,18 @@ public function editRenderSettingButton(DC_Table $dataContainer)
public function getAttributeNames(DC_Table $objDc)
{
$attributeNames = [
- 'sorting' => $GLOBALS['TL_LANG']['MSC']['metamodels_sorting'],
- 'random' => $GLOBALS['TL_LANG']['MSC']['random'],
- 'id' => $GLOBALS['TL_LANG']['MSC']['id'][0]
+ 'sorting' => $this->translator->trans('metamodels_sorting', [], 'metamodels_list'),
+ 'random' => $this->translator->trans('random', [], 'metamodels_list'),
+ 'id' => $this->translator->trans('id', [], 'metamodels_list')
];
- $metaModelName = $this->factory->translateIdToMetaModelName($objDc->activeRecord->metamodel);
+ assert(null !== $objDc->activeRecord);
+ try {
+ $metaModelName = $this->factory->translateIdToMetaModelName($objDc->activeRecord->metamodel);
+ } catch (RuntimeException $exception) {
+ // No valid MetaModel selected, can not add attributes of it.
+ return $attributeNames;
+ }
$metaModel = $this->factory->getMetaModel($metaModelName);
if ($metaModel) {
@@ -248,7 +281,7 @@ public function getAttributeNames(DC_Table $objDc)
}
/**
- * Fetch all available filter settings for the current meta model.
+ * Fetch all available filter settings for the current MetaModel.
*
* @param DC_Table $objDC The data container calling this method.
*
@@ -256,13 +289,14 @@ public function getAttributeNames(DC_Table $objDc)
*/
public function getFilterSettings(DC_Table $objDC)
{
+ assert(null !== $objDC->activeRecord);
$filterSettings = $this->connection->createQueryBuilder()
->select('f.id', 'f.name')
->from('tl_metamodel_filter', 'f')
->where('f.pid=:id')
->setParameter('id', $objDC->activeRecord->metamodel)
- ->execute()
- ->fetchAll(\PDO::FETCH_ASSOC);
+ ->executeQuery()
+ ->fetchAllAssociative();
$result = [];
foreach ($filterSettings as $filterSetting) {
@@ -287,6 +321,9 @@ public function getFilterSettings(DC_Table $objDC)
*/
public function getMetaTitleAttributes(DC_Table $objDC)
{
+ assert(null !== $objDC->activeRecord);
+
+ /** @psalm-suppress ArgumentTypeCoercion - We HOPE there is a list of strings. */
return $this->getFilteredAttributeNames(
$objDC->activeRecord->metamodel,
(array) $GLOBALS['METAMODELS']['metainformation']['allowedTitle']
@@ -305,6 +342,9 @@ public function getMetaTitleAttributes(DC_Table $objDC)
*/
public function getMetaDescriptionAttributes(DC_Table $objDC)
{
+ assert(null !== $objDC->activeRecord);
+
+ /** @psalm-suppress ArgumentTypeCoercion - We HOPE there is a list of strings. */
return $this->getFilteredAttributeNames(
$objDC->activeRecord->metamodel,
(array) $GLOBALS['METAMODELS']['metainformation']['allowedDescription']
@@ -319,16 +359,18 @@ public function getMetaDescriptionAttributes(DC_Table $objDC)
*
* @return void
*
+ * @throws Exception
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
protected function buildFilterParamsFor(DC_Table $dataContainer, $elementName)
{
- if (!$this->requestStack->getCurrentRequest()->query->get('act')) {
+ $request = $this->requestStack->getCurrentRequest();
+ if (null === $request || !$request->query->has('act')) {
return;
}
- $filterId = $this->connection->createQueryBuilder()
+ $filterIds = $this->connection->createQueryBuilder()
->select('c.metamodel_filtering')
->from(static::$tableName, 'c')
->join('c', 'tl_metamodel', 'mm', 'mm.id=c.metamodel')
@@ -337,10 +379,10 @@ protected function buildFilterParamsFor(DC_Table $dataContainer, $elementName)
->andWhere('c.type=:type')
->setParameter('type', $elementName)
->setMaxResults(1)
- ->execute()
- ->fetch(\PDO::FETCH_COLUMN);
+ ->executeQuery()
+ ->fetchFirstColumn();
- if (!$filterId) {
+ if (false === ($filterId = reset($filterIds)) || 0 === $filterId) {
unset($GLOBALS['TL_DCA'][static::$tableName]['fields']['metamodel_filterparams']);
return;
}
@@ -349,8 +391,11 @@ protected function buildFilterParamsFor(DC_Table $dataContainer, $elementName)
$dca = $collection->getParameterDCA();
foreach ($dca as $fieldName => $subField) {
$options = [];
- foreach ($subField['options'] as $key => $value) {
- $options[$this->loadCallback($key)] = $value;
+ foreach (($subField['options'] ?? []) as $key => $value) {
+ $newKey = $this->loadCallback($key);
+ if (null !== $newKey) {
+ $options[$newKey] = $value;
+ }
}
$dca[$fieldName]['options'] = $options;
@@ -371,7 +416,7 @@ protected function buildFilterParamsFor(DC_Table $dataContainer, $elementName)
*/
public function saveCallback(string $value = null)
{
- return null === $value ? null : \base64_decode($value);
+ return null === $value ? null : base64_decode($value);
}
/**
@@ -383,7 +428,7 @@ public function saveCallback(string $value = null)
*/
public function loadCallback(string $value = null)
{
- return null === $value ? null : trim(\base64_encode($value), '=');
+ return null === $value ? null : trim(base64_encode($value), '=');
}
/**
@@ -395,16 +440,16 @@ public function loadCallback(string $value = null)
*/
public function getFilterParameterNames(DC_Table $objDc)
{
- $return = array();
+ assert(null !== $objDc->activeRecord);
+
+ $return = [];
$filter = $objDc->activeRecord->metamodel_filtering;
if (!$filter) {
return $return;
}
- $collection = $this->filterFactory->createCollection($filter);
-
- return $collection->getParameterFilterNames();
+ return $this->filterFactory->createCollection($filter)->getParameterFilterNames();
}
/**
@@ -416,6 +461,8 @@ public function getFilterParameterNames(DC_Table $objDc)
*/
public function getFilterTemplates(DC_Table $dcTable)
{
+ assert(null !== $dcTable->activeRecord);
+
if ($dcTable->activeRecord->type === 'metamodels_frontendclearall') {
return $this->templateList->getTemplatesForBase('mm_clearall_');
}
@@ -436,7 +483,7 @@ protected function getTemplateList($base)
}
/**
- * Fetch all available render settings for the current meta model.
+ * Fetch all available render settings for the current MetaModel.
*
* @param DC_Table $objDC The data container calling this method.
*
@@ -444,13 +491,15 @@ protected function getTemplateList($base)
*/
public function getRenderSettings(DC_Table $objDC)
{
+ assert(null !== $objDC->activeRecord);
+
$filterSettings = $this->connection->createQueryBuilder()
->select('r.id', 'r.name')
->from('tl_metamodel_rendersettings', 'r')
->where('r.pid=:id')
->setParameter('id', $objDC->activeRecord->metamodel)
- ->execute()
- ->fetchAll(\PDO::FETCH_ASSOC);
+ ->executeQuery()
+ ->fetchAllAssociative();
$result = [];
foreach ($filterSettings as $filterSetting) {
@@ -472,7 +521,7 @@ public function getRenderSettings(DC_Table $objDC)
*
* @return string
*/
- private function renderEditButton($caption, $title, UrlBuilder $url)
+ private function renderEditButton(string $caption, string $title, UrlBuilder $url): string
{
$icon = $this->iconBuilder->getBackendIconImageTag(
'system/themes/flexible/icons/alias.svg',
@@ -493,19 +542,24 @@ private function renderEditButton($caption, $title, UrlBuilder $url)
*
* If the optional parameter arrTypes is not given, all attributes will be retrieved.
*
- * @param int $metaModelId The id of the MetaModel from which the attributes shall be retrieved from.
- *
- * @param string[] $allowedTypes The attribute type names that shall be retrieved (optional).
+ * @param string $metaModelId The id of the MetaModel from which the attributes shall be retrieved from.
+ * @param list $allowedTypes The attribute type names that shall be retrieved.
*
* @return array A list with all found attributes.
*/
- private function getFilteredAttributeNames($metaModelId, $allowedTypes = array())
+ private function getFilteredAttributeNames(string $metaModelId, array $allowedTypes): array
{
- $attributeNames = array();
+ $attributeNames = [];
- if ($metaModel = $this->factory->getMetaModel($this->factory->translateIdToMetaModelName($metaModelId))) {
+ try {
+ $metaModelName = $this->factory->translateIdToMetaModelName($metaModelId);
+ } catch (RuntimeException $exception) {
+ // No valid MetaModel selected, can not add attributes of it.
+ return $attributeNames;
+ }
+ if ($metaModel = $this->factory->getMetaModel($metaModelName)) {
foreach ($metaModel->getAttributes() as $attribute) {
- if (empty($allowedTypes) || in_array($attribute->get('type'), $allowedTypes)) {
+ if (empty($allowedTypes) || in_array($attribute->get('type'), $allowedTypes, true)) {
$attributeNames[$attribute->getColName()] =
sprintf(
'%s [%s]',
diff --git a/src/CoreBundle/Contao/Hooks/ContentElementCallback.php b/src/CoreBundle/Contao/Hooks/ContentElementCallback.php
index ede808094..f04697d1b 100644
--- a/src/CoreBundle/Contao/Hooks/ContentElementCallback.php
+++ b/src/CoreBundle/Contao/Hooks/ContentElementCallback.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2021 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,7 @@
* @author Marc Reimann
* @author Stefan Heimes
* @author Ingolf Steinhardt
- * @copyright 2012-2021 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -60,9 +60,9 @@ public function buildFilterParameterList(DC_Table $dataContainer)
*/
public function getTemplates(DC_Table $objDC)
{
- /** @noinspection PhpUndefinedFieldInspection */
+ assert(null !== $objDC->activeRecord);
$type = $objDC->activeRecord->type;
- if ($type == 'metamodel_content') {
+ if ($type === 'metamodel_content') {
$type = 'metamodel_list';
}
diff --git a/src/CoreBundle/Contao/Hooks/FixupUserGroupModules.php b/src/CoreBundle/Contao/Hooks/FixupUserGroupModules.php
deleted file mode 100644
index 60256d1a4..000000000
--- a/src/CoreBundle/Contao/Hooks/FixupUserGroupModules.php
+++ /dev/null
@@ -1,148 +0,0 @@
-
- * @author Ben
- * @copyright 2012-2019 The MetaModels team.
- * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
- * @filesource
- */
-
-namespace MetaModels\CoreBundle\Contao\Hooks;
-
-use Contao\DataContainer;
-use MetaModels\ViewCombination\InputScreenInformationBuilder;
-use MetaModels\ViewCombination\ViewCombinationBuilder;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-/**
- * This is called as a HOOK from tl_user_group.
- */
-class FixupUserGroupModules
-{
- /**
- * The view combination builder.
- *
- * @var ViewCombinationBuilder
- */
- private $combinationBuilder;
-
- /**
- * The input screen information builder.
- *
- * @var InputScreenInformationBuilder
- */
- private $inputScreens;
-
- /**
- * The request stack.
- *
- * @var RequestStack
- */
- protected $requestStack;
-
- /**
- * Create a new instance.
- *
- * @param ViewCombinationBuilder $combinationBuilder The view combination builder.
- * @param InputScreenInformationBuilder $inputScreens The input screen information builder.
- * @param RequestStack $requestStack The request stack.
- */
- public function __construct(
- ViewCombinationBuilder $combinationBuilder,
- InputScreenInformationBuilder $inputScreens,
- RequestStack $requestStack
- ) {
- $this->combinationBuilder = $combinationBuilder;
- $this->inputScreens = $inputScreens;
- $this->requestStack = $requestStack;
- }
-
- /**
- * Fix up the modules in the backend.
- *
- * @param DataContainer $dataContainer The current data container.
- *
- * @return array
- *
- * @throws \RuntimeException When the "parenting" class can not be found.
- */
- public function fixupModules(DataContainer $dataContainer)
- {
- if (!class_exists('tl_user_group', false)) {
- throw new \RuntimeException('data container is not loaded!');
- }
-
- $original = new \tl_user_group();
- $modules = $original->getModules($dataContainer);
-
- // 1. remove all MetaModels
- foreach (array_keys($modules) as $group) {
- foreach ($modules[$group] as $key => $module) {
- if (strpos($module, 'metamodel_') === 0) {
- unset($modules[$group][$key]);
- }
- }
- // Otherwise we end up with an associative array.
- $modules[$group] = array_values($modules[$group]);
- }
-
- // 2. Add our "custom" modules and remove the main module.
- $modules['metamodels'][] = 'support_metamodels';
- if (false !== $index = array_search('metamodels', $modules['metamodels'], true)) {
- unset($modules['metamodels'][$index]);
- $modules['metamodels'] = array_values($modules['metamodels']);
- }
-
- // 3. Add back all MetaModels for the current group.
- $combinations = $this->combinationBuilder->getCombinationsForUser([$dataContainer->activeRecord->id], 'be');
-
- $screenIds = array_map(function ($combination) {
- return $combination['dca_id'];
- }, $combinations['byName']);
-
- $screens = $this->inputScreens->fetchInputScreens($screenIds);
-
- $locale = $this->requestStack->getCurrentRequest()->getLocale();
- foreach ($screens as $metaModel => $screen) {
- if ('standalone' === $screen['meta']['rendertype']) {
- $modules[$screen['meta']['backendsection']][] = 'metamodel_' . $metaModel;
- $this->buildLanguageString('metamodel_' . $metaModel, $screen, $locale);
- }
- }
-
- return $modules;
- }
-
- /**
- * Build the language string for the passed backend module.
- *
- * @param string $name The module name.
- * @param array $screen The input screen information.
- * @param string $locale The locale.
- *
- * @return void
- *
- * @SuppressWarnings(PHPMD.Superglobals)
- * @SuppressWarnings(PHPMD.CamelCaseVariableName)
- */
- private function buildLanguageString($name, $screen, $locale)
- {
- if (isset($screen['label'][$locale])) {
- $GLOBALS['TL_LANG']['MOD'][$name] = $screen['label'][$locale];
- return;
- }
-
- $GLOBALS['TL_LANG']['MOD'][$name] = $screen['label'][''];
- }
-}
diff --git a/src/CoreBundle/Contao/Hooks/LoadDataContainer.php b/src/CoreBundle/Contao/Hooks/LoadDataContainer.php
index bb6512379..08ce3af38 100644
--- a/src/CoreBundle/Contao/Hooks/LoadDataContainer.php
+++ b/src/CoreBundle/Contao/Hooks/LoadDataContainer.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -23,8 +23,12 @@
use Contao\Controller;
use Contao\CoreBundle\Framework\Adapter;
+use Contao\StringUtil;
+use Contao\System;
+use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use MetaModels\CoreBundle\Assets\IconBuilder;
+use MetaModels\Helper\LocaleUtil;
use MetaModels\IFactory;
use MetaModels\IMetaModel;
use MetaModels\ViewCombination\ViewCombination;
@@ -37,30 +41,30 @@ class LoadDataContainer
/**
* Adapter to the Contao\Controller class.
*
- * @var Controller
+ * @var Adapter
*/
- private $controller;
+ private Adapter $controller;
/**
* The MetaModels factory.
*
* @var IFactory
*/
- private $factory;
+ private IFactory $factory;
/**
* The view combination.
*
* @var ViewCombination
*/
- private $combination;
+ private ViewCombination $combination;
/**
* The icon builder.
*
* @var IconBuilder
*/
- private $iconBuilder;
+ private IconBuilder $iconBuilder;
/**
* Create a new instance.
@@ -89,22 +93,20 @@ public function __construct(
*
* @return void
*/
- public function onLoadDataContainer($tableName)
+ public function onLoadDataContainer($tableName): void
{
- // @codingStandardsIgnoreStart
- // FIXME: make this beautiful.
- // @codingStandardsIgnoreEnd
- if (!\System::getContainer()->get('cca.dc-general.scope-matcher')->currentScopeIsBackend()) {
+ $scopeMatcher = System::getContainer()->get('cca.dc-general.scope-matcher');
+ if (!($scopeMatcher instanceof RequestScopeDeterminator) || !$scopeMatcher->currentScopeIsBackend()) {
return;
}
static $tableExists;
// Test that the tables have been created.
if (null === $tableExists) {
- $tableExists = \System::getContainer()
- ->get('database_connection')
- ->getSchemaManager()
- ->tablesExist(['tl_metamodel']);
+ if (null === ($connection = System::getContainer()->get('database_connection'))) {
+ return;
+ }
+ $tableExists = $connection->createSchemaManager()->tablesExist(['tl_metamodel']);
}
if (false === $tableExists) {
return;
@@ -124,14 +126,14 @@ public function onLoadDataContainer($tableName)
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- private function handleMetaModelTable($tableName)
+ private function handleMetaModelTable(string $tableName): void
{
static $tableNames;
if (!$tableNames) {
$tableNames = $this->factory->collectNames();
}
// Not a MetaModel, get out now.
- if (!in_array($tableName, $tableNames)) {
+ if (!\in_array($tableName, $tableNames)) {
return;
}
@@ -141,7 +143,7 @@ private function handleMetaModelTable($tableName)
$GLOBALS['TL_DCA'][$tableName] = [];
}
- $GLOBALS['TL_DCA'][$tableName] = array_replace_recursive(
+ $GLOBALS['TL_DCA'][$tableName] = \array_replace_recursive(
(array) $GLOBALS['TL_DCA']['tl_metamodel_item'],
(array) $GLOBALS['TL_DCA'][$tableName]
);
@@ -157,10 +159,10 @@ private function handleMetaModelTable($tableName)
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- private function handleNonMetaModelTable($tableName)
+ private function handleNonMetaModelTable(string $tableName): void
{
// Nothing to do for MetaModel tables.
- if (substr($tableName, 0, 3) === 'mm_') {
+ if (\str_starts_with($tableName, 'mm_')) {
return;
}
@@ -179,16 +181,17 @@ private function handleNonMetaModelTable($tableName)
$this->controller->loadLanguageFile('default');
foreach ($map[$tableName] as $metaModelTable => $inputScreen) {
$metaModel = $this->factory->getMetaModel($metaModelTable);
- $caption = $this->buildCaption($metaModel, $inputScreen);
+ assert($metaModel instanceof IMetaModel);
+
+ $caption = $this->buildCaption($metaModel, $inputScreen);
$operationName = 'edit_' . $metaModel->getTableName();
- $parentDCA['list']['operations'][$operationName] = array
- (
+ $parentDCA['list']['operations'][$operationName] = [
'label' => &$caption,
'href' => 'table=' . $metaModelTable,
'icon' => $this->iconBuilder->getBackendIcon($inputScreen['meta']['backendicon']),
'attributes' => 'onclick="Backend.getScrollOffset()"',
- );
+ ];
// Is the destination table a metamodel with variants?
if ($metaModel->hasVariants()) {
@@ -202,7 +205,15 @@ private function handleNonMetaModelTable($tableName)
$idParameter =
$parentDCA['list']['operations'][$operationName]['idparam'];
$parentDCA['list']['operations'][$operationName]['button_callback'] =
- function ($row, $href, $label, $name, $icon, $attributes, $table) use ($idParameter) {
+ function (
+ array $row,
+ string $href,
+ string $label,
+ string $name,
+ string $icon,
+ string $attributes,
+ string $table
+ ) use ($idParameter): string {
return $this->buildChildOperationButton(
$idParameter,
$row['id'],
@@ -223,7 +234,7 @@ function ($row, $href, $label, $name, $icon, $attributes, $table) use ($idParame
*
* @return array
*/
- private function buildMap()
+ private function buildMap(): array
{
$map = [];
foreach ($this->combination->getParented() as $childName => $child) {
@@ -244,14 +255,15 @@ private function buildMap()
* @SuppressWarnings(PHPMD.Superglobals)
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- private function buildCaption($metaModel, $inputScreen): array
+ private function buildCaption(IMetaModel $metaModel, array $inputScreen): array
{
$caption = [
- sprintf($GLOBALS['TL_LANG']['MSC']['metamodel_edit_as_child']['label'], $metaModel->getName()),
+ \sprintf($GLOBALS['TL_LANG']['MSC']['metamodel_edit_as_child']['label'], $metaModel->getName()),
''
];
- $currentLanguage = \str_replace('-', '_', $GLOBALS['TL_LANGUAGE']);
+ // @deprecated usage of TL_LANGUAGE - remove for Contao 5.0.
+ $currentLanguage = LocaleUtil::formatAsLocale($GLOBALS['TL_LANGUAGE']);
foreach ($inputScreen['label'] as $langCode => $label) {
if ($label !== '' && $langCode === $currentLanguage) {
$caption = [
@@ -268,25 +280,26 @@ private function buildCaption($metaModel, $inputScreen): array
* This method exists only for being compatible when MetaModels are being used as child table from DC_Table context.
*
* @param string $idParameter The id parameter in use.
- *
- * @param string $itemId The current data row.
- *
- * @param string $href The href to be appended.
- *
- * @param string $label The operation label.
- *
- * @param string $name The operation name.
- *
- * @param string $icon The icon path.
- *
- * @param string $attributes The button attributes.
- *
- * @param string $table The table name.
+ * @param string $itemId The current data row.
+ * @param string $href The href to be appended.
+ * @param string $label The operation label.
+ * @param string $name The operation name.
+ * @param string $icon The icon path.
+ * @param string $attributes The button attributes.
+ * @param string $table The table name.
*
* @return string
*/
- private function buildChildOperationButton($idParameter, $itemId, $href, $label, $name, $icon, $attributes, $table)
- {
+ private function buildChildOperationButton(
+ string $idParameter,
+ string $itemId,
+ string $href,
+ string $label,
+ string $name,
+ string $icon,
+ string $attributes,
+ string $table
+ ): string {
$modelId = ModelId::fromValues($table, $itemId);
$url = $href . '&' . $idParameter . '=' . $modelId->getSerialized();
@@ -297,14 +310,15 @@ private function buildChildOperationButton($idParameter, $itemId, $href, $label,
$url = $this->controller->addToUrl($url);
// If id parameter different, we have to clean out the id in the URL now.
if ('id' !== $idParameter) {
- $url = preg_replace('#(&)id=(?:&)?#', '$1', $url);
+ $url = \preg_replace('#(&)id=(?:&)?#', '$1', $url);
}
- $title = sprintf($label ?: $name, $itemId);
- return sprintf(
+ $title = \sprintf($label ?: $name, $itemId);
+
+ return \sprintf(
'%4$s ',
$url,
- specialchars($title),
+ StringUtil::specialchars($title),
$attributes,
$this->iconBuilder->getBackendIconImageTag($icon, $label)
);
diff --git a/src/CoreBundle/Contao/Hooks/ModuleCallback.php b/src/CoreBundle/Contao/Hooks/ModuleCallback.php
index cfb5c1644..7879dc3d6 100644
--- a/src/CoreBundle/Contao/Hooks/ModuleCallback.php
+++ b/src/CoreBundle/Contao/Hooks/ModuleCallback.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2021 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,13 +13,15 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Ingolf Steinhardt
- * @copyright 2012-2021 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\Contao\Hooks;
+use Contao\DC_Table;
+
/**
* This class provides callbacks for tl_module.
*/
@@ -35,11 +37,11 @@ class ModuleCallback extends AbstractContentElementAndModuleCallback
/**
* Called from tl_content.onload_callback.
*
- * @param \DC_Table $dataContainer The data container calling this method.
+ * @param DC_Table $dataContainer The data container calling this method.
*
* @return void
*/
- public function buildFilterParameterList(\DC_Table $dataContainer)
+ public function buildFilterParameterList(DC_Table $dataContainer)
{
parent::buildFilterParamsFor($dataContainer, 'metamodel_list');
}
@@ -47,12 +49,13 @@ public function buildFilterParameterList(\DC_Table $dataContainer)
/**
* Fetch the template group for the current MetaModel frontend module.
*
- * @param \DC_Table $objDC The data container calling this method.
+ * @param DC_Table $objDC The data container calling this method.
*
* @return array
*/
- public function getTemplates(\DC_Table $objDC)
+ public function getTemplates(DC_Table $objDC)
{
+ assert(null !== $objDC->activeRecord);
$type = $objDC->activeRecord->type;
return $this->getTemplateList('mod_' . $type);
diff --git a/src/CoreBundle/Contao/InsertTag/ReplaceParam.php b/src/CoreBundle/Contao/InsertTag/ReplaceParam.php
index 8e6f7c31c..2b0d67068 100644
--- a/src/CoreBundle/Contao/InsertTag/ReplaceParam.php
+++ b/src/CoreBundle/Contao/InsertTag/ReplaceParam.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -17,7 +17,7 @@
* @author Oliver Hoff
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -27,8 +27,10 @@
namespace MetaModels\CoreBundle\Contao\InsertTag;
use Contao\CoreBundle\Framework\Adapter;
+use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\Input;
-use Contao\Session;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
/**
* This replaces the insert tag param.
@@ -36,29 +38,36 @@
final class ReplaceParam
{
/**
- * The input.
+ * The input framework.
*
- * @var Input
+ * @var ContaoFramework
*/
- private $input;
+ private ContaoFramework $framework;
/**
- * The session.
+ * The adapter.
*
- * @var Session
+ * @var Adapter|null
*/
- private $session;
+ private ?Adapter $input = null;
+
+ /**
+ * The request stack.
+ *
+ * @var RequestStack
+ */
+ private RequestStack $requestStack;
/**
* ReplaceParam constructor.
*
- * @param Adapter $input The input.
- * @param Session $session The session.
+ * @param ContaoFramework $framework The input framework.
+ * @param RequestStack $requestStack The session.
*/
- public function __construct(Adapter $input, Session $session)
+ public function __construct(ContaoFramework $framework, RequestStack $requestStack)
{
- $this->input = $input;
- $this->session = $session;
+ $this->framework = $framework;
+ $this->requestStack = $requestStack;
}
/**
@@ -71,7 +80,9 @@ public function __construct(Adapter $input, Session $session)
*/
public function replace(string $content): ?string
{
- if (false === \strpos($content, '{{')
+ $tags = [];
+ if (
+ !\str_contains($content, '{{')
|| !($tags = preg_split('@\{\{(.*)\}\}@', $content, -1, PREG_SPLIT_DELIM_CAPTURE))
|| (\count($tags) < 2)
) {
@@ -80,7 +91,8 @@ public function replace(string $content): ?string
$newContent = null;
foreach ($tags as $tag) {
- if (!(2 === \count($chunks = \explode('::', $tag, 2)))
+ if (
+ !(2 === \count($chunks = \explode('::', $tag, 2)))
|| !('param' === $chunks[0])
|| !($this->isParameterSupported($chunks[1], ['get', 'post', 'cookie', 'session', 'filter']))
) {
@@ -96,28 +108,33 @@ public function replace(string $content): ?string
/**
* Replace the insert tag with the input value.
*
- * @param array $chunks The chunks.
- * @param string|null $content The content.
- * @param string $tag The tag.
+ * @param list $chunks The chunks.
+ * @param string|null $content The content.
+ * @param string $tag The tag.
*
* @return string|null
+ *
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function replaceInputParameter(array $chunks, ?string $content, string $tag): ?string
{
- if ((null === $content)
- || !($this->isParameterSupported($chunks[1], ['get', 'post', 'cookie']))
- || !($arguments = $this->splitParameter($chunks[1]))
- ) {
+ if (null === ($arguments = $this->getArguments($chunks[1], $content, ['get', 'post', 'cookie']))) {
return $content;
}
+ assert(\is_string($content));
- if ((false === \strpos($tag, '&default='))) {
+ if (null === $this->input) {
+ /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */
+ $this->input = $this->framework->getAdapter(Input::class);
+ }
+
+ if ((!\str_contains($tag, '&default='))) {
if (null === ($result = $this->input->{$arguments[0]}($arguments[1]))) {
return null;
}
return \str_replace(
'{{' . $tag . '}}',
- \is_array($result) ? \serialize($result) : $result,
+ \is_array($result) ? \serialize($result) : ($result ?? ''),
$content
);
}
@@ -125,7 +142,7 @@ private function replaceInputParameter(array $chunks, ?string $content, string $
$result = ($this->input->{$arguments[0]}($arguments[1]) ?: $arguments[2]);
return \str_replace(
'{{' . $tag . '}}',
- \is_array($result) ? \serialize($result) : $result,
+ \is_array($result) ? \serialize($result) : ($result ?? ''),
$content
);
}
@@ -133,23 +150,24 @@ private function replaceInputParameter(array $chunks, ?string $content, string $
/**
* Replace the insert tag with the session value.
*
- * @param array $chunks The chunks.
- * @param string|null $content The content.
- * @param string $tag The tag.
+ * @param list $chunks The chunks.
+ * @param string|null $content The content.
+ * @param string $tag The tag.
*
* @return string|null
*/
private function replaceSessionParameter(array $chunks, ?string $content, string $tag): ?string
{
- if ((null === $content)
- || !($this->isParameterSupported($chunks[1], ['session']))
- || !($arguments = $this->splitParameter($chunks[1]))
- ) {
+ if (null === ($arguments = $this->getArguments($chunks[1], $content, ['session']))) {
return $content;
}
+ assert(\is_string($content));
- if ((false === \strpos($tag, '&default='))) {
- $result = $this->session->get($arguments[1]);
+ $sessionBag = $this->requestStack->getSession()->getBag('contao_frontend');
+ assert($sessionBag instanceof AttributeBagInterface);
+
+ if ((!\str_contains($tag, '&default='))) {
+ $result = $sessionBag->get($arguments[1]);
return \str_replace(
'{{' . $tag . '}}',
\is_array($result) ? \serialize($result) : (string) $result,
@@ -157,7 +175,7 @@ private function replaceSessionParameter(array $chunks, ?string $content, string
);
}
- $result = ($this->session->get($arguments[1]) ?: $arguments[2]);
+ $result = ($sessionBag->get($arguments[1]) ?: $arguments[2]);
return \str_replace(
'{{' . $tag . '}}',
\is_array($result) ? \serialize($result) : (string) $result,
@@ -165,22 +183,41 @@ private function replaceSessionParameter(array $chunks, ?string $content, string
);
}
+ /**
+ * @param list $supported
+ *
+ * @return list|null
+ */
+ private function getArguments(string $chunk, ?string $content, array $supported): ?array
+ {
+ if ((null === $content) || !$this->isParameterSupported($chunk, $supported)) {
+ return null;
+ }
+ $arguments = $this->splitParameter($chunk);
+ if ((null === $arguments) || ([] === $arguments)) {
+ return null;
+ }
+
+ return $arguments;
+ }
+
/**
* Split the parameter.
*
* @param string $parameter The parameter.
*
- * @return array|null
+ * @return list|null
*/
private function splitParameter(string $parameter): ?array
{
- if ((2 !== \count($chunks = \explode('?', $parameter)))
- || (0 !== \strpos($chunks[1], 'name='))
+ if (
+ (2 !== \count($chunks = \explode('?', $parameter)))
+ || (!\str_starts_with($chunks[1], 'name='))
) {
return null;
}
- if (false === \strpos($chunks[1], '&default=')) {
+ if (!\str_contains($chunks[1], '&default=')) {
return [$chunks[0], \substr($chunks[1], \strlen('name='))];
}
@@ -203,7 +240,7 @@ private function isParameterSupported(string $parameter, array $supported): bool
{
$isSupported = false;
foreach ($supported as $name) {
- if (0 !== \strpos($parameter, $name)) {
+ if (!\str_starts_with($parameter, $name)) {
continue;
}
diff --git a/src/CoreBundle/Contao/InsertTag/ResolveLanguageTag.php b/src/CoreBundle/Contao/InsertTag/ResolveLanguageTag.php
index 47d092b32..d1f6e7377 100644
--- a/src/CoreBundle/Contao/InsertTag/ResolveLanguageTag.php
+++ b/src/CoreBundle/Contao/InsertTag/ResolveLanguageTag.php
@@ -13,6 +13,7 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Oliver Willmes
+ * @author Ingolf Steinhardt
* @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
@@ -60,14 +61,15 @@ public function __construct(RequestStack $requestStack)
public function resolve(string $queryString): string
{
// @codingStandardsIgnoreStart
- if (\strpos($queryString, '{{iflng') === false && \strpos($queryString, '{{ifnlng') === false) {
+ if (!\str_contains($queryString, '{{iflng') && !\str_contains($queryString, '{{ifnlng')) {
return $queryString;
}
$tags = \preg_split('~{{(ifn?lng[^{}]*)}}~', $queryString, -1, PREG_SPLIT_DELIM_CAPTURE );
$strBuffer = '';
+ $arrCache = [];
- for ($_rit=0, $_cnt=\count($tags); $_rit<$_cnt; $_rit+=2) {
+ for ($_rit = 0, $_cnt = \count($tags); $_rit < $_cnt; $_rit += 2) {
$strBuffer .= $tags[$_rit];
if (!isset($tags[$_rit+1])) {
@@ -81,12 +83,12 @@ public function resolve(string $queryString): string
$flags = \explode('|', $strTag);
$tag = \array_shift($flags);
- $elements = \explode('::', $tag);
+ $elements = \array_merge(\explode('::', $tag), ['']);
$arrCache[$strTag] = '';
if (
- !empty($elements[1]) &&
+ '' !== $elements[1] &&
$this->languageMatches($elements[1]) === (\strtolower($elements[0]) === 'ifnlng')
) {
for (; $_rit<$_cnt; $_rit+=2) {
diff --git a/src/CoreBundle/ContaoManager/Plugin.php b/src/CoreBundle/ContaoManager/Plugin.php
index 5545faa07..0d882b31c 100644
--- a/src/CoreBundle/ContaoManager/Plugin.php
+++ b/src/CoreBundle/ContaoManager/Plugin.php
@@ -59,8 +59,10 @@ public function getBundles(ParserInterface $parser)
*/
public function getRouteCollection(LoaderResolverInterface $resolver, KernelInterface $kernel)
{
- return $resolver
- ->resolve(__DIR__.'/../Resources/config/routing.yml')
- ->load(__DIR__.'/../Resources/config/routing.yml');
+ if (false === ($resolved = $resolver->resolve(__DIR__ . '/../Resources/config/routing.yml'))) {
+ return null;
+ }
+
+ return $resolved->load(__DIR__ . '/../Resources/config/routing.yml');
}
}
diff --git a/src/CoreBundle/Controller/Backend/AbstractAddAllController.php b/src/CoreBundle/Controller/Backend/AbstractAddAllController.php
index 3000399d5..1d5981599 100644
--- a/src/CoreBundle/Controller/Backend/AbstractAddAllController.php
+++ b/src/CoreBundle/Controller/Backend/AbstractAddAllController.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,7 @@
* @author Sven Baumann
* @author Richard Henkenjohann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -32,55 +32,59 @@
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment as TwigEnvironment;
/**
* This controller provides the base for the add-all handlers for input screens and render settings.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
abstract class AbstractAddAllController
{
/**
* Adapter to the Contao\System class.
*
- * @var System
+ * @var Adapter
*/
- private $systemAdapter;
+ private Adapter $systemAdapter;
/**
* The translator.
*
* @var TranslatorInterface
*/
- private $translator;
+ private TranslatorInterface $translator;
/**
* The MetaModels factory.
*
* @var IFactory
*/
- private $factory;
+ private IFactory $factory;
/**
* The database connection.
*
* @var Connection
*/
- private $connection;
+ private Connection $connection;
/**
* The cache purger.
*
* @var PurgeCache
*/
- private $purger;
+ private PurgeCache $purger;
/**
* The list of known attributes.
*
* @var array
*/
- private $knownAttributes;
+ private array $knownAttributes;
/**
* The twig engine.
@@ -94,7 +98,7 @@ abstract class AbstractAddAllController
*
* @var int
*/
- private $startSort;
+ private int $startSort;
/**
* Create a new instance.
@@ -103,7 +107,7 @@ abstract class AbstractAddAllController
* @param TranslatorInterface $translator The translator.
* @param IFactory $factory The MetaModels factory.
* @param Connection $connection The database connection.
- * @param Adapter $systemAdapter Adapter to the Contao\System class.
+ * @param Adapter $systemAdapter Adapter to the Contao\System class.
* @param PurgeCache $purger The cache purger.
*/
public function __construct(
@@ -129,10 +133,11 @@ public function __construct(
* @param string $parentId The parent id.
* @param bool $activate Flag if the setting shall get activated.
* @param int $sort The sorting value.
+ * @param string $tlclass The CSS class.
*
* @return array
*/
- abstract protected function createEmptyDataFor(IAttribute $attribute, $parentId, $activate, $sort);
+ abstract protected function createEmptyDataFor(IAttribute $attribute, $parentId, $activate, $sort, $tlclass = '');
/**
* Test if the passed attribute is acceptable.
@@ -194,18 +199,18 @@ protected function render($table, $metaModel, Request $request)
return [
'action' => '',
- 'requestToken' => REQUEST_TOKEN,
+ 'requestToken' => System::getContainer()->get('contao.csrf.token_manager')?->getDefaultTokenValue(),
'href' => $this->getReferer($request, $table, true),
- 'backBt' => $this->translator->trans('MSC.backBT', [], 'contao_default'),
- 'add' => $this->translator->trans('MSC.continue', [], 'contao_default'),
- 'saveNclose' => $this->translator->trans('MSC.saveNclose', [], 'contao_default'),
- 'activate' => $this->translator->trans($table . '.addAll_activate', [], 'contao_' . $table),
+ 'backBt' => $this->translator->trans('backBT', [], $table),
+ 'add' => $this->translator->trans('continue', [], $table),
+ 'saveNclose' => $this->translator->trans('saveNclose', [], $table),
+ 'activate' => $this->translator->trans('addAll_activate', [], $table),
'tlclass' => '',
- 'headline' => $this->translator->trans($table . '.addall.1', [], 'contao_' . $table),
- 'selectAll' => $this->translator->trans('MSC.selectAll', [], 'contao_default') . '.',
+ 'headline' => $this->translator->trans('addall.description', [], $table),
+ 'selectAll' => $this->translator->trans('selectAll', [], $table) . '.',
'cacheMessage' => '',
'updateMessage' => '',
- 'hasCheckbox' => count($fields) > 0,
+ 'hasCheckbox' => \count($fields) > 0,
'fields' => $fields,
'stylesheets' => ['bundles/metamodelscore/css/style.css']
];
@@ -219,7 +224,7 @@ protected function render($table, $metaModel, Request $request)
*
* @return array
*/
- private function fetchExisting($table, $parentId)
+ private function fetchExisting(string $table, string $parentId): array
{
// Keep the sorting value.
$this->startSort = 0;
@@ -232,9 +237,9 @@ private function fetchExisting($table, $parentId)
->where('t.pid=:pid')
->setParameter('pid', $parentId)
->orderBy('t.sorting')
- ->execute();
+ ->executeQuery();
- foreach ($alreadyExisting->fetchAll(\PDO::FETCH_ASSOC) as $item) {
+ foreach ($alreadyExisting->fetchAllAssociative() as $item) {
$this->knownAttributes[$item['attr_id']] = $item;
$this->startSort = $item['sorting'];
}
@@ -249,9 +254,9 @@ private function fetchExisting($table, $parentId)
*
* @return bool
*/
- private function knowsAttribute($attribute)
+ private function knowsAttribute(IAttribute $attribute): bool
{
- return array_key_exists($attribute->get('id'), $this->knownAttributes);
+ return \array_key_exists($attribute->get('id'), $this->knownAttributes);
}
/**
@@ -263,7 +268,7 @@ private function knowsAttribute($attribute)
*
* @return array
*/
- private function generateForm($table, $metaModel, Request $request)
+ private function generateForm(string $table, IMetaModel $metaModel, Request $request): array
{
$fields = [];
// Loop over all attributes now.
@@ -312,12 +317,16 @@ private function generateForm($table, $metaModel, Request $request)
*
* @return string
*/
- private function checkboxCaption($key, $table, IAttribute $attribute)
+ private function checkboxCaption(string $key, string $table, IAttribute $attribute): string
{
return $this->translator->trans(
- $table . '.' . $key,
- [$attribute->getName(), $attribute->get('type'), $attribute->getColName()],
- 'contao_' . $table
+ $key,
+ [
+ '%name%' => $attribute->getName(),
+ '%type%' => $attribute->get('type'),
+ '%colName%' => $attribute->getColName()
+ ],
+ $table
);
}
@@ -329,7 +338,7 @@ private function checkboxCaption($key, $table, IAttribute $attribute)
*
* @return bool
*/
- private function isAttributeSubmitted($attributeId, Request $request)
+ private function isAttributeSubmitted(string $attributeId, Request $request): bool
{
return $request->request->has('attribute_' . $attributeId);
}
@@ -344,37 +353,41 @@ private function isAttributeSubmitted($attributeId, Request $request)
*
* @return void
*/
- private function perform($table, Request $request, $metaModel, $parentId)
+ private function perform(string $table, Request $request, IMetaModel $metaModel, string $parentId): void
{
$activate = (bool) $request->request->get('activate');
- $tlclass = $request->request->get('tlclass');
+ $tlclass = (string) $request->request->get('tlclass');
$query = $this
->connection
->createQueryBuilder()
->insert($table);
foreach ($metaModel->getAttributes() as $attribute) {
- if ($this->knowsAttribute($attribute)
+ if (
+ $this->knowsAttribute($attribute)
|| !($this->accepts($attribute) && $this->isAttributeSubmitted($attribute->get('id'), $request))
) {
continue;
}
$data = [];
- foreach ($this->createEmptyDataFor(
- $attribute,
- $parentId,
- $activate,
- $this->startSort,
- $tlclass
- ) as $key => $value) {
+ foreach (
+ $this->createEmptyDataFor(
+ $attribute,
+ $parentId,
+ $activate,
+ $this->startSort,
+ $tlclass
+ ) as $key => $value
+ ) {
$data[$key] = ':' . $key;
$query->setParameter($key, $value);
}
- $query->values($data)->execute();
+ $query->values($data)->executeQuery();
$this->startSort += 128;
}
+
$this->purger->purge();
}
@@ -387,7 +400,7 @@ private function perform($table, Request $request, $metaModel, $parentId)
*
* @return string
*/
- private function getReferer(Request $request, $table, $encodeAmp = false)
+ private function getReferer(Request $request, string $table, bool $encodeAmp = false): string
{
$uri = $this->systemAdapter->getReferer($encodeAmp, $table);
// Make the location an absolute URL
diff --git a/src/CoreBundle/Controller/Backend/ConfigurationController.php b/src/CoreBundle/Controller/Backend/ConfigurationController.php
new file mode 100644
index 000000000..6795b63cb
--- /dev/null
+++ b/src/CoreBundle/Controller/Backend/ConfigurationController.php
@@ -0,0 +1,96 @@
+
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+namespace MetaModels\CoreBundle\Controller\Backend;
+
+use Contao\CoreBundle\Framework\ContaoFramework;
+use ContaoCommunityAlliance\DcGeneral\Factory\DcGeneralFactoryService;
+use ContaoCommunityAlliance\Translator\TranslatorInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Twig\Environment as TwigEnvironment;
+use Twig\Error\LoaderError;
+use Twig\Error\RuntimeError;
+use Twig\Error\SyntaxError;
+
+final class ConfigurationController
+{
+ use DcGeneralControllerTrait;
+
+ /**
+ * @param Request $request The request.
+ * @param TwigEnvironment $twig The twig environment.
+ * @param DcGeneralFactoryService $factoryFactory The DCG factory
+ * @param EventDispatcherInterface $dispatcher The event dispatcher.
+ * @param TranslatorInterface $translator The translator.
+ * @param ContaoFramework $framework The Contao framework
+ *
+ * @return Response
+ * @throws LoaderError
+ * @throws RuntimeError
+ * @throws SyntaxError
+ */
+ public function __invoke(
+ Request $request,
+ TwigEnvironment $twig,
+ DcGeneralFactoryService $factoryFactory,
+ EventDispatcherInterface $dispatcher,
+ TranslatorInterface $translator,
+ ContaoFramework $framework,
+ ): Response {
+ $containerName = (string) $request->query->get('table', 'tl_metamodel');
+ $controllerResult = $this->bootDcGeneralAndProcess(
+ $request,
+ $containerName,
+ $factoryFactory,
+ $dispatcher,
+ $translator,
+ $framework
+ );
+ $headline = $this->determineHeadline($containerName, $translator);
+
+ return new Response(
+ $twig->render(
+ '@MetaModelsCore/Backend/be_config.html.twig',
+ [
+ 'headline' => $headline,
+ 'body' => $controllerResult,
+ 'stylesheets' => ['bundles/metamodelscore/css/style.css']
+ ]
+ )
+ );
+ }
+
+ /**
+ * Generate headline.
+ *
+ * @param string $containerName The container.
+ * @param TranslatorInterface $translator The translator.
+ *
+ * @return string
+ *
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ private function determineHeadline(string $containerName, TranslatorInterface $translator): string
+ {
+ return $translator->translate('backend-module.headline', $containerName);
+ }
+}
diff --git a/src/CoreBundle/Controller/Backend/DcGeneralControllerTrait.php b/src/CoreBundle/Controller/Backend/DcGeneralControllerTrait.php
new file mode 100644
index 000000000..5651e4b2c
--- /dev/null
+++ b/src/CoreBundle/Controller/Backend/DcGeneralControllerTrait.php
@@ -0,0 +1,94 @@
+
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+namespace MetaModels\CoreBundle\Controller\Backend;
+
+use Contao\Ajax;
+use Contao\Controller;
+use Contao\CoreBundle\Framework\ContaoFramework;
+use ContaoCommunityAlliance\DcGeneral\Action;
+use ContaoCommunityAlliance\DcGeneral\Clipboard\ClipboardInterface;
+use ContaoCommunityAlliance\DcGeneral\Controller\ControllerInterface;
+use ContaoCommunityAlliance\DcGeneral\Factory\DcGeneralFactoryService;
+use ContaoCommunityAlliance\Translator\TranslatorInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+trait DcGeneralControllerTrait
+{
+ /**
+ * @param Request $request The request.
+ * @param string $tableName The table name
+ * @param DcGeneralFactoryService $factoryFactory The DCG factory
+ * @param EventDispatcherInterface $dispatcher The event dispatcher.
+ * @param TranslatorInterface $translator The translator.
+ * @param ContaoFramework $framework The Contao framework
+ *
+ * @return string
+ * @throws \Exception
+ */
+ public function bootDcGeneralAndProcess(
+ Request $request,
+ string $tableName,
+ DcGeneralFactoryService $factoryFactory,
+ EventDispatcherInterface $dispatcher,
+ TranslatorInterface $translator,
+ ContaoFramework $framework,
+ ): string {
+ $act = (string) $request->query->get('act', 'showAll');
+
+ // Work around legacy Contao code.
+ /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */
+ $contaoController = $framework->getAdapter(Controller::class);
+ // Need to load the language file due to Widget class using hardcoded lang array offsets.
+ $contaoController->loadLanguageFile('default');
+
+ // Handle Ajax calls.
+ $action = null;
+ if ($request->isXmlHttpRequest() && '' !== ($action = (string) $request->request->get('action', ''))) {
+ $ajaxClass = new Ajax($action);
+ $ajaxClass->executePreActions();
+ }
+
+ // Build data container.
+ $factory = $factoryFactory->createFactory();
+ $general = $factory
+ ->setContainerName($tableName)
+ ->setTranslator($translator)
+ ->setEventDispatcher($dispatcher)
+ ->createDcGeneral();
+
+ $environment = $general->getEnvironment();
+ $clipboard = $environment->getClipboard();
+ assert($clipboard instanceof ClipboardInterface);
+
+ // Load the clipboard.
+ $clipboard->loadFrom($environment);
+
+ $controller = $environment->getController();
+ assert($controller instanceof ControllerInterface);
+
+ if (null !== $action) {
+ $environment->getView()?->handleAjaxCall();
+ }
+
+ return $controller->handle(new Action($act));
+ }
+}
diff --git a/src/CoreBundle/Controller/Backend/InputScreenAddAllController.php b/src/CoreBundle/Controller/Backend/InputScreenAddAllController.php
index 57185a847..4106bd209 100644
--- a/src/CoreBundle/Controller/Backend/InputScreenAddAllController.php
+++ b/src/CoreBundle/Controller/Backend/InputScreenAddAllController.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -30,7 +30,7 @@
use MetaModels\IMetaModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment as TwigEnvironment;
/**
@@ -43,7 +43,7 @@ class InputScreenAddAllController extends AbstractAddAllController
*
* @var TranslatorInterface
*/
- private $translator;
+ private TranslatorInterface $translator;
/**
* The twig engine.
@@ -102,12 +102,20 @@ protected function render($table, $metaModel, Request $request)
{
return \array_merge(
parent::render($table, $metaModel, $request),
- ['tlclass' => $this->translator->trans($table . '.addAll_tlclass', [], 'contao_' . $table)]
+ ['tlclass' => $this->translator->trans('addAll_tlclass', [], $table)]
);
}
/**
- * {@inheritDoc}
+ * Create an empty data set for inclusion into the database.
+ *
+ * @param IAttribute $attribute The attribute to generate the data for.
+ * @param string $parentId The parent id.
+ * @param bool $activate Flag if the setting shall get activated.
+ * @param int $sort The sorting value.
+ * @param string $tlclass The CSS class.
+ *
+ * @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -119,7 +127,7 @@ protected function createEmptyDataFor(IAttribute $attribute, $parentId, $activat
'attr_id' => $attribute->get('id'),
'pid' => $parentId,
'sorting' => $sort,
- 'tstamp' => time(),
+ 'tstamp' => \time(),
'published' => $activate ? '1' : ''
];
}
diff --git a/src/CoreBundle/Controller/Backend/MetaModelController.php b/src/CoreBundle/Controller/Backend/MetaModelController.php
new file mode 100644
index 000000000..7dfe28bc6
--- /dev/null
+++ b/src/CoreBundle/Controller/Backend/MetaModelController.php
@@ -0,0 +1,114 @@
+
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+namespace MetaModels\CoreBundle\Controller\Backend;
+
+use Contao\CoreBundle\Exception\AccessDeniedException;
+use Contao\CoreBundle\Framework\ContaoFramework;
+use Contao\CoreBundle\Menu\BackendMenuBuilder;
+use ContaoCommunityAlliance\DcGeneral\Factory\DcGeneralFactoryService;
+use ContaoCommunityAlliance\Translator\TranslatorInterface;
+use MetaModels\ViewCombination\ViewCombination;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Twig\Environment as TwigEnvironment;
+
+final class MetaModelController
+{
+ use DcGeneralControllerTrait;
+
+ /**
+ * The constructor.
+ *
+ * @param BackendMenuBuilder $builder The menu builder.
+ */
+ public function __construct(
+ private readonly BackendMenuBuilder $builder
+ ) {
+ }
+
+ /**
+ * @param Request $request The request.
+ * @param TwigEnvironment $twig The twig environment.
+ * @param DcGeneralFactoryService $factoryFactory The DCG factory
+ * @param EventDispatcherInterface $dispatcher The event dispatcher.
+ * @param TranslatorInterface $translator The translator.
+ * @param ContaoFramework $framework The Contao framework
+ *
+ * @return Response
+ */
+ public function __invoke(
+ Request $request,
+ TwigEnvironment $twig,
+ DcGeneralFactoryService $factoryFactory,
+ EventDispatcherInterface $dispatcher,
+ TranslatorInterface $translator,
+ ContaoFramework $framework,
+ ViewCombination $viewCombination,
+ ): Response {
+ $containerName = (string) $request->query->get('table', '');
+ if ('' === $containerName) {
+ $containerName = (string) ($request->attributes->get('_route_params', [])['tableName'] ?? '');
+ }
+ $combination = $viewCombination->getCombination($containerName);
+ if (null === $combination) {
+ throw new AccessDeniedException('Permission denied to access back end module "' . $containerName . '".');
+ }
+ $inputScreenId = $combination['dca_id'] ?? '';
+ $controllerResult = $this->bootDcGeneralAndProcess(
+ $request,
+ $containerName,
+ $factoryFactory,
+ $dispatcher,
+ $translator,
+ $framework
+ );
+ $headline = $this->determineHeadline($containerName, $inputScreenId, $translator);
+
+ return new Response(
+ $twig->render(
+ '@MetaModelsCore/Backend/be_config.html.twig',
+ [
+ 'headline' => $headline,
+ 'body' => $controllerResult,
+ 'stylesheets' => ['bundles/metamodelscore/css/style.css']
+ ]
+ )
+ );
+ }
+
+ /**
+ * Generate headline.
+ *
+ * @param string $containerName The container.
+ * @param string $inputScreenId The input screen id.
+ * @param TranslatorInterface $translator The translator.
+ *
+ * @return string
+ */
+ private function determineHeadline(
+ string $containerName,
+ string $inputScreenId,
+ TranslatorInterface $translator
+ ): string {
+ return $translator->translate('backend-module.' . $inputScreenId . '.headline', $containerName);
+ }
+}
diff --git a/src/CoreBundle/Controller/Backend/RenderSettingAddAllController.php b/src/CoreBundle/Controller/Backend/RenderSettingAddAllController.php
index 83431be7f..effff2e9f 100644
--- a/src/CoreBundle/Controller/Backend/RenderSettingAddAllController.php
+++ b/src/CoreBundle/Controller/Backend/RenderSettingAddAllController.php
@@ -49,7 +49,7 @@ public function __invoke($metaModel, $renderSetting, Request $request)
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- protected function createEmptyDataFor(IAttribute $attribute, $parentId, $activate, $sort)
+ protected function createEmptyDataFor(IAttribute $attribute, $parentId, $activate, $sort, $tlclass = '')
{
$result = [
'attr_id' => $attribute->get('id'),
diff --git a/src/CoreBundle/Controller/Backend/SupportMetaModelsController.php b/src/CoreBundle/Controller/Backend/SupportMetaModelsController.php
index 68c004008..7eef890e2 100644
--- a/src/CoreBundle/Controller/Backend/SupportMetaModelsController.php
+++ b/src/CoreBundle/Controller/Backend/SupportMetaModelsController.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2022 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,8 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Richard Henkenjohann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2022 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -22,7 +23,7 @@
namespace MetaModels\CoreBundle\Controller\Backend;
use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment as TwigEnvironment;
/**
@@ -88,20 +89,20 @@ public function __invoke()
'stylesheets' => [
'bundles/metamodelscore/css/supportscreen.css'
],
- 'headline' => $this->translator->trans('MOD.support_metamodels.0', [], 'contao_modules'),
+ 'headline' => $this->translator->trans('menu.label', [], 'metamodels_support'),
'sub_headline' =>
- $this->translator->trans('MSC.metamodels_support.main_headline', [], 'contao_default'),
+ $this->translator->trans('main_headline', [], 'metamodels_support'),
'head_contributor' =>
- $this->translator->trans('MSC.metamodels_support.contributor_headline', [], 'contao_default'),
- 'purpose' => $this->translator->trans('MSC.metamodels_support.purpose', [], 'contao_default'),
+ $this->translator->trans('contributor_headline', [], 'metamodels_support'),
+ 'purpose' => $this->translator->trans('purpose', [], 'metamodels_support'),
'other_donations' =>
- $this->translator->trans('MSC.metamodels_support.other_donations', [], 'contao_default'),
+ $this->translator->trans('other_donations', [], 'metamodels_support'),
'main_text' =>
- $this->translator->trans('MSC.metamodels_support.main_text', [], 'contao_default'),
+ $this->translator->trans('main_text', [], 'metamodels_support'),
'help_headline' =>
- $this->translator->trans('MSC.metamodels_support.help_headline', [], 'contao_default'),
+ $this->translator->trans('help_headline', [], 'metamodels_support'),
'help_text' =>
- $this->translator->trans('MSC.metamodels_support.help_text', [], 'contao_default'),
+ $this->translator->trans('help_text', [], 'metamodels_support'),
'github_contributors' => $this->getJsonFile($this->github),
'transifex_contributors' => $this->getJsonFile($this->transifex)
]
diff --git a/src/CoreBundle/Controller/ContentElement/ItemListController.php b/src/CoreBundle/Controller/ContentElement/ItemListController.php
index 0180b7258..f73870b28 100644
--- a/src/CoreBundle/Controller/ContentElement/ItemListController.php
+++ b/src/CoreBundle/Controller/ContentElement/ItemListController.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,7 @@
* @author Ingolf Steinhardt
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2020 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -26,6 +26,7 @@
use Contao\CoreBundle\Controller\ContentElement\AbstractContentElementController;
use Contao\CoreBundle\ServiceAnnotation\ContentElement;
use Contao\PageModel;
+use Contao\System;
use Contao\Template;
use MetaModels\CoreBundle\Controller\ListControllerTrait;
use Symfony\Component\HttpFoundation\Request;
@@ -35,6 +36,8 @@
* The item list content element.
*
* @ContentElement("metamodel_content", category="metamodels", template="ce_metamodel_content")
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
final class ItemListController extends AbstractContentElementController
{
@@ -58,11 +61,12 @@ public function __invoke(
array $classes = null,
PageModel $pageModel = null
): Response {
- if ($this->get('contao.routing.scope_matcher')->isBackendRequest($request)) {
+ if ($this->scopeMatcher->isBackendRequest($request)) {
return $this->getBackendWildcard($model);
}
- if (!empty($model->metamodel_layout)) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ if (null !== $model->metamodel_layout) {
$model->customTpl = $model->metamodel_layout;
}
@@ -78,7 +82,7 @@ public function __invoke(
*
* @return Response The response.
*/
- protected function getResponse(Template $template, ContentModel $model, Request $request): ?Response
+ protected function getResponse(Template $template, ContentModel $model, Request $request): Response
{
$response = $this->getResponseInternal($template, $model, $request);
$this->addSharedMaxAgeToResponse($response, $model);
@@ -95,8 +99,8 @@ protected function getResponse(Template $template, ContentModel $model, Request
*/
private function getBackendWildcard(ContentModel $model): Response
{
- $name = $this->get('translator')->trans('CTE.' . $this->getType() . '.0', [], 'contao_modules');
- $href = $this->get('router')->generate(
+ $name = $this->translator->trans($this->getType(), [], 'metamodels_wildcard');
+ $href = $this->router->generate(
'contao_backend',
['do' => 'article', 'table' => 'tl_content', 'act' => 'edit', 'id' => $model->id]
);
diff --git a/src/CoreBundle/Controller/FrontendModule/ItemListController.php b/src/CoreBundle/Controller/FrontendModule/ItemListController.php
index 35fff15e7..c5f4f0839 100644
--- a/src/CoreBundle/Controller/FrontendModule/ItemListController.php
+++ b/src/CoreBundle/Controller/FrontendModule/ItemListController.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,8 @@
* @author Richard Henkenjohann
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2020 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -34,6 +35,8 @@
* The item list front end module.
*
* @FrontendModule("metamodel_list", category="metamodels")
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
final class ItemListController extends AbstractFrontendModuleController
{
@@ -57,7 +60,12 @@ public function __invoke(
array $classes = null,
PageModel $pageModel = null
): Response {
- if (!empty($model->metamodel_layout)) {
+ if ($this->scopeMatcher->isBackendRequest($request)) {
+ return $this->getBackendWildcard($model);
+ }
+
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ if (null !== $model->metamodel_layout) {
$model->customTpl = $model->metamodel_layout;
}
@@ -73,8 +81,8 @@ public function __invoke(
*/
protected function getBackendWildcard(ModuleModel $module): Response
{
- $name = $this->get('translator')->trans('FMD.'.$this->getType().'.0', [], 'contao_modules');
- $href = $this->get('router')->generate(
+ $name = $this->translator->trans($this->getType(), [], 'metamodels_wildcard');
+ $href = $this->router->generate(
'contao_backend',
['do' => 'themes', 'table' => 'tl_module', 'act' => 'edit', 'id' => $module->id]
);
@@ -91,7 +99,7 @@ protected function getBackendWildcard(ModuleModel $module): Response
*
* @return Response The response.
*/
- protected function getResponse(Template $template, ModuleModel $model, Request $request): ?Response
+ protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
{
return $this->getResponseInternal($template, $model, $request);
}
diff --git a/src/CoreBundle/Controller/ListControllerTrait.php b/src/CoreBundle/Controller/ListControllerTrait.php
index 0b6c546bd..6c0359785 100644
--- a/src/CoreBundle/Controller/ListControllerTrait.php
+++ b/src/CoreBundle/Controller/ListControllerTrait.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,7 +15,7 @@
* @author Christian Schiffler
* @author Ingolf Steinhardt
* @author Sven Baumann
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -23,20 +23,26 @@
namespace MetaModels\CoreBundle\Controller;
use Contao\BackendTemplate;
+use Contao\CoreBundle\Routing\ScopeMatcher;
use Contao\Input;
use Contao\Model;
use Contao\StringUtil;
+use Contao\System;
use Contao\Template;
use MetaModels\Filter\FilterUrl;
use MetaModels\Filter\FilterUrlBuilder;
use MetaModels\Filter\Setting\IFilterSettingFactory;
+use MetaModels\Helper\SortingLinkGenerator;
use MetaModels\IFactory;
use MetaModels\IItem;
+use MetaModels\IMetaModel;
use MetaModels\ItemList;
use MetaModels\Render\Setting\IRenderSettingFactory;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Helper trait for lists (CE and MOD).
@@ -78,6 +84,27 @@ trait ListControllerTrait
*/
private FilterUrlBuilder $filterUrlBuilder;
+ /**
+ * The translator.
+ *
+ * @var TranslatorInterface
+ */
+ private TranslatorInterface $translator;
+
+ /**
+ * The router.
+ *
+ * @var RouterInterface
+ */
+ private RouterInterface $router;
+
+ /**
+ * The scope matcher.
+ *
+ * @var ScopeMatcher
+ */
+ private ScopeMatcher $scopeMatcher;
+
/**
* ItemListController constructor.
*
@@ -86,19 +113,64 @@ trait ListControllerTrait
* @param IRenderSettingFactory $renderSettingFactory The render setting factory (required in MetaModels 3.0).
* @param EventDispatcherInterface $eventDispatcher The event dispatcher (required in MetaModels 3.0).
* @param FilterUrlBuilder $filterUrlBuilder The filter url builder.
+ * @param TranslatorInterface|null $translator The translator.
+ * @param RouterInterface|null $router The router.
+ * @param ScopeMatcher|null $scopeMatcher The scope matcher.
*/
public function __construct(
IFactory $factory,
IFilterSettingFactory $filterFactory,
IRenderSettingFactory $renderSettingFactory,
EventDispatcherInterface $eventDispatcher,
- FilterUrlBuilder $filterUrlBuilder
+ FilterUrlBuilder $filterUrlBuilder,
+ TranslatorInterface $translator = null,
+ RouterInterface $router = null,
+ ScopeMatcher $scopeMatcher = null
) {
$this->factory = $factory;
$this->filterFactory = $filterFactory;
$this->renderSettingFactory = $renderSettingFactory;
$this->eventDispatcher = $eventDispatcher;
$this->filterUrlBuilder = $filterUrlBuilder;
+
+ if (null === $translator) {
+ // @codingStandardsIgnoreStart
+ @trigger_error(
+ 'Translator is missing. It has to be passed in the constructor. Fallback will be dropped.',
+ E_USER_DEPRECATED
+ );
+ // @codingStandardsIgnoreEnd
+
+ $translator = System::getContainer()->get('translator');
+ assert($translator instanceof TranslatorInterface);
+ }
+ $this->translator = $translator;
+
+ if (null === $router) {
+ // @codingStandardsIgnoreStart
+ @trigger_error(
+ 'Router is missing. It has to be passed in the constructor. Fallback will be dropped.',
+ E_USER_DEPRECATED
+ );
+ // @codingStandardsIgnoreEnd
+
+ $router = System::getContainer()->get('router');
+ assert($router instanceof RouterInterface);
+ }
+ $this->router = $router;
+
+ if (null === $scopeMatcher) {
+ // @codingStandardsIgnoreStart
+ @trigger_error(
+ 'ScopeMatcher is missing. It has to be passed in the constructor. Fallback will be dropped.',
+ E_USER_DEPRECATED
+ );
+ // @codingStandardsIgnoreEnd
+
+ $scopeMatcher = System::getContainer()->get('contao.routing.scope_matcher');
+ assert($scopeMatcher instanceof ScopeMatcher);
+ }
+ $this->scopeMatcher = $scopeMatcher;
}
/**
@@ -112,10 +184,14 @@ public function __construct(
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
- private function getResponseInternal(Template $template, Model $model, Request $request): ?Response
+ private function getResponseInternal(Template $template, Model $model, Request $request): Response
{
- if (empty($pageParam = $model->metamodel_page_param)) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ if ('' === ($pageParam = $model->metamodel_page_param)) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
switch ($model->type) {
case 'metamodel_content':
$pageParam = 'page_mmce' . $model->id;
@@ -128,6 +204,7 @@ private function getResponseInternal(Template $template, Model $model, Request $
}
}
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
$itemRenderer = new ItemList(
$this->factory,
$this->filterFactory,
@@ -141,35 +218,59 @@ private function getResponseInternal(Template $template, Model $model, Request $
$model->metamodel_pagination_urlfragment
);
+ /**
+ * @psalm-suppress UndefinedMagicPropertyAssignment
+ * @psalm-suppress UndefinedMagicPropertyFetch
+ */
$template->searchable = !$model->metamodel_donotindex;
- $sorting = $model->metamodel_sortby;
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $sorting = $model->metamodel_sortby;
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
$direction = $model->metamodel_sortby_direction;
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $sortParamType = $model->metamodel_sort_param_type;
+ $sortOrderByParam = 'orderBy';
+ $sortOrderDirParam = 'orderDir';
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $sortOverride = $model->metamodel_sort_override;
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $sortFragment = $model->metamodel_sort_urlfragment;
// @codingStandardsIgnoreStart
// FIXME: filter URL should be created from local request and not from master request.
// @codingStandardsIgnoreEnd
$filterUrl = $this->filterUrlBuilder->getCurrentFilterUrl();
- if ($model->metamodel_sort_override) {
- if (null !==
+ if ($sortOverride) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $sortOrderByParam = $model->metamodel_order_by_param ?: $sortOrderByParam;
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $sortOrderDirParam = $model->metamodel_order_dir_param ?: $sortOrderDirParam;
+ if (
+ null !==
$value = $this->tryReadFromSlugOrGet(
$filterUrl,
- ($model->metamodel_order_by_param ?: 'orderBy'),
- $model->metamodel_sort_param_type
- )) {
+ $sortOrderByParam,
+ $sortParamType
+ )
+ ) {
$sorting = $value;
}
- if (null !==
+ if (
+ null !==
$value = $this->tryReadFromSlugOrGet(
$filterUrl,
- ($model->metamodel_order_dir_param ?: 'orderDir'),
- $model->metamodel_sort_param_type
- )) {
+ $sortOrderDirParam,
+ $sortParamType
+ )
+ ) {
$direction = $value;
}
}
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
$filterParams = StringUtil::deserialize($model->metamodel_filterparams, true);
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
$itemRenderer
->setMetaModel($model->metamodel, $model->metamodel_rendersettings)
->setListTemplate($template)
@@ -179,20 +280,45 @@ private function getResponseInternal(Template $template, Model $model, Request $
->setFilterSettings($model->metamodel_filtering)
->setFilterParameters($filterParams, $this->getFilterParameters($filterUrl, $itemRenderer))
->setMetaTags($model->metamodel_meta_title, $model->metamodel_meta_description);
+ if ($sortOverride) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ $itemRenderer->setSortingLinkGenerator(
+ new SortingLinkGenerator(
+ $this->filterUrlBuilder,
+ $this->translator,
+ $sortParamType,
+ $sortOrderByParam,
+ $sortOrderDirParam,
+ $sortFragment,
+ $model->metamodel_sortby,
+ $model->metamodel_sortby_direction
+ )
+ );
+ }
- foreach (StringUtil::deserialize(($model->metamodel_parameters ?? null), true) as $key => $value) {
- $itemRenderer->setTemplateParameter($key, $value);
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ if ($model->metamodel_use_parameters) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ foreach (StringUtil::deserialize(($model->metamodel_parameters ?? null), true) as $key => $value) {
+ $itemRenderer->setTemplateParameter($key, $value);
+ }
}
+ /**
+ * @psalm-suppress UndefinedMagicPropertyAssignment
+ * @psalm-suppress UndefinedMagicPropertyFetch
+ */
$template->items = StringUtil::encodeEmail($itemRenderer->render($model->metamodel_noparsing, $model));
+ /** @psalm-suppress UndefinedMagicPropertyAssignment */
$template->numberOfItems = $itemRenderer->getItems()->getCount();
+ /** @psalm-suppress UndefinedMagicPropertyAssignment */
$template->pagination = $itemRenderer->getPagination();
- $responseTags = array_map(
+ $responseTags = \array_map(
static function (IItem $item) {
- return sprintf('contao.db.%s.%d', $item->getMetaModel()->getTableName(), $item->get('id'));
+ return \sprintf('contao.db.%s.%d', $item->getMetaModel()->getTableName(), $item->get('id'));
},
- iterator_to_array($itemRenderer->getItems(), false)
+ \iterator_to_array($itemRenderer->getItems(), false)
);
$response = $template->getResponse();
@@ -267,11 +393,14 @@ private function renderBackendWildcard(string $href, string $name, Model $model)
{
$template = new BackendTemplate('be_wildcard');
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
$headline = StringUtil::deserialize($model->headline);
-
+ /** @psalm-suppress UndefinedMagicPropertyAssignment */
$template->wildcard = $this->getWildcardInfoText($model, $href, $name);
- $template->title = (\is_array($headline) ? $headline['value'] : $headline);
- $template->id = $model->id;
+ /** @psalm-suppress UndefinedMagicPropertyAssignment */
+ $template->title = (\is_array($headline) ? $headline['value'] : $headline);
+ /** @psalm-suppress UndefinedMagicPropertyAssignment */
+ $template->id = $model->id;
return new Response($template->parse());
}
@@ -292,67 +421,73 @@ private function renderBackendWildcard(string $href, string $name, Model $model)
*/
private function getWildcardInfoText(Model $model, string $href, string $name): string
{
- if (empty($model->metamodel)) {
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
+ if (null === $model->metamodel) {
return 'MetaModel not configured.';
}
- if (null === $metaModelName = $this->factory->translateIdToMetaModelName($model->metamodel)) {
+ if ('' === ($metaModelName = $this->factory->translateIdToMetaModelName($model->metamodel))) {
return 'Unknown MetaModel: ' . $model->metamodel;
}
// Add CSS file.
$GLOBALS['TL_CSS'][] = 'bundles/metamodelscore/css/style.css';
- // Retrieve name of MetaModels.
+ // Retrieve name of MetaModel.
$infoTemplate =
'';
$metaModel = $this->factory->getMetaModel($metaModelName);
- $header = $name . ': ' . $metaModel->getName();
+ assert($metaModel instanceof IMetaModel);
+
+ $header = $metaModel->getName();
if ($href) {
- $header .= sprintf(
+ $header .= \sprintf(
' (ID: %3$s)',
$href,
- REQUEST_TOKEN,
- $model->id
+ System::getContainer()->get('contao.csrf.token_manager')?->getDefaultTokenValue() ?? '',
+ (string) $model->id
);
}
- $infoText = sprintf(
+ $infoText = '### ' . $name . ' ###' . \sprintf(
$infoTemplate,
- $this->get('translator')->trans('MSC.mm_be_info_name.1', [], 'contao_default'),
- $this->get('translator')->trans('MSC.mm_be_info_name.0', [], 'contao_default'),
- $header
+ $this->translator->trans('mm_be_info_name.description', [], 'metamodels_wildcard'),
+ $this->translator->trans('mm_be_info_name.label', [], 'metamodels_wildcard'),
+ $header,
);
// Retrieve name of filter.
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
if ($model->metamodel_filtering) {
$filterparams = [];
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
foreach (StringUtil::deserialize($model->metamodel_filterparams, true) as $filterparam) {
if ($filterparam['value']) {
$filterparams[] = $filterparam['value'];
}
}
- $infoFiPa = count($filterparams) ? ': ' . implode(', ', $filterparams) : '';
+ $infoFiPa = \count($filterparams) ? ': ' . \implode(', ', $filterparams) : '';
$infoFi = $this->filterFactory->createCollection($model->metamodel_filtering)->get('name');
- if ($infoFi) {
- $infoText .= sprintf(
+ if (null !== $infoFi) {
+ $infoText .= \sprintf(
$infoTemplate,
- $this->get('translator')->trans('MSC.mm_be_info_filter.1', [], 'contao_default'),
- $this->get('translator')->trans('MSC.mm_be_info_filter.0', [], 'contao_default'),
+ $this->translator->trans('mm_be_info_filter.description', [], 'metamodels_wildcard'),
+ $this->translator->trans('mm_be_info_filter.label', [], 'metamodels_wildcard'),
$infoFi . $infoFiPa
);
}
}
- // Retrieve name of rendersetting.
+ // Retrieve name of render setting.
+ /** @psalm-suppress UndefinedMagicPropertyFetch */
if ($model->metamodel_rendersettings) {
$infoRs = $this->renderSettingFactory
->createCollection($metaModel, $model->metamodel_rendersettings)
->get('name');
- if ($infoRs) {
- $infoText .= sprintf(
+ if (null !== $infoRs) {
+ $infoText .= \sprintf(
$infoTemplate,
- $this->get('translator')->trans('MSC.mm_be_info_render_setting.1', [], 'contao_default'),
- $this->get('translator')->trans('MSC.mm_be_info_render_setting.0', [], 'contao_default'),
+ $this->translator->trans('mm_be_info_render_setting.description', [], 'metamodels_wildcard'),
+ $this->translator->trans('mm_be_info_render_setting.label', [], 'metamodels_wildcard'),
$infoRs
);
}
diff --git a/src/CoreBundle/DcGeneral/AbstractAttributeConditionFactory.php b/src/CoreBundle/DcGeneral/AbstractAttributeConditionFactory.php
index 82ec437b5..efb20328a 100644
--- a/src/CoreBundle/DcGeneral/AbstractAttributeConditionFactory.php
+++ b/src/CoreBundle/DcGeneral/AbstractAttributeConditionFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,7 +12,8 @@
*
* @package MetaModels/core
* @author Christian Schiffler
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -30,8 +31,7 @@ abstract class AbstractAttributeConditionFactory implements AttributeAwareProper
* Extract the attribute instance from the MetaModel.
*
* @param IMetaModel $metaModel The MetaModel instance.
- *
- * @param string $attributeId The attribute id.
+ * @param int $attributeId The attribute id.
*
* @return string
*
diff --git a/src/CoreBundle/DcGeneral/AbstractRestrictedAttributeConditionFactory.php b/src/CoreBundle/DcGeneral/AbstractRestrictedAttributeConditionFactory.php
index 4f472a68a..79641839a 100644
--- a/src/CoreBundle/DcGeneral/AbstractRestrictedAttributeConditionFactory.php
+++ b/src/CoreBundle/DcGeneral/AbstractRestrictedAttributeConditionFactory.php
@@ -23,6 +23,7 @@
* This is the abstract base for attribute aware condition factories.
*
* @SuppressWarnings(PHPMD.LongClassName)
+ * @SuppressWarnings(PHPMD.LongVariable)
*/
abstract class AbstractRestrictedAttributeConditionFactory extends AbstractAttributeConditionFactory
{
diff --git a/src/CoreBundle/DcGeneral/FallbackPropertyConditionFactory.php b/src/CoreBundle/DcGeneral/FallbackPropertyConditionFactory.php
index d368959d8..c497c98a9 100644
--- a/src/CoreBundle/DcGeneral/FallbackPropertyConditionFactory.php
+++ b/src/CoreBundle/DcGeneral/FallbackPropertyConditionFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2021 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2021 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -37,7 +38,7 @@ class FallbackPropertyConditionFactory
*
* @var EventDispatcherInterface
*/
- private $dispatcher;
+ private EventDispatcherInterface $dispatcher;
/**
* Create a new instance.
@@ -158,7 +159,9 @@ public function supportsAttribute($conditionType, $attribute)
*/
public function createCondition(array $configuration, IMetaModel $metaModel)
{
+ /** @psalm-suppress DeprecatedClass */
$event = new CreatePropertyConditionEvent($configuration, $metaModel);
+ /** @psalm-suppress DeprecatedClass */
$this->dispatcher->dispatch($event, CreatePropertyConditionEvent::NAME);
if (null === $instance = $event->getInstance()) {
diff --git a/src/CoreBundle/DcGeneral/PropertyConditionFactory.php b/src/CoreBundle/DcGeneral/PropertyConditionFactory.php
index fd37f0181..77b4ade10 100644
--- a/src/CoreBundle/DcGeneral/PropertyConditionFactory.php
+++ b/src/CoreBundle/DcGeneral/PropertyConditionFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,7 +12,8 @@
*
* @package MetaModels/core
* @author Christian Schiffler
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -25,6 +26,8 @@
/**
* This factory takes care of building property conditions.
+ *
+ * @psalm-suppress DeprecatedClass
*/
class PropertyConditionFactory
{
@@ -33,20 +36,22 @@ class PropertyConditionFactory
*
* @var IdProvidingServiceLocator
*/
- private $factories;
+ private IdProvidingServiceLocator $factories;
/**
* The fallback factory.
*
* @var FallbackPropertyConditionFactory
*/
- private $fallbackFactory;
+ private FallbackPropertyConditionFactory $fallbackFactory;
/**
* Create a new instance.
*
* @param IdProvidingServiceLocator $factories The factories.
* @param FallbackPropertyConditionFactory $fallbackFactory The fallback factory.
+ *
+ * @psalm-suppress DeprecatedClass
*/
public function __construct(IdProvidingServiceLocator $factories, FallbackPropertyConditionFactory $fallbackFactory)
{
@@ -138,11 +143,7 @@ public function createCondition(array $configuration, IMetaModel $metaModel)
}
if (!$this->factories->has($typeName = $configuration['type'])) {
- if ($result = $this->fallbackFactory->createCondition($configuration, $metaModel)) {
- return $result;
- }
-
- throw new \InvalidArgumentException('Unknown type: ' . $typeName);
+ return $this->fallbackFactory->createCondition($configuration, $metaModel);
}
return $this->getFactory($typeName)->buildCondition($configuration, $metaModel);
diff --git a/src/CoreBundle/DependencyInjection/CompilerPass/CollectDoctrineSchemaGeneratorsPass.php b/src/CoreBundle/DependencyInjection/CompilerPass/CollectDoctrineSchemaGeneratorsPass.php
new file mode 100644
index 000000000..d4493a479
--- /dev/null
+++ b/src/CoreBundle/DependencyInjection/CompilerPass/CollectDoctrineSchemaGeneratorsPass.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2012-2019 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+declare(strict_types=1);
+
+namespace MetaModels\CoreBundle\DependencyInjection\CompilerPass;
+
+use MetaModels\Schema\Doctrine\DoctrineSchemaGenerator;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * This pass adds the tagged schema generators to the doctrine engine.
+ */
+class CollectDoctrineSchemaGeneratorsPass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ public const TAG_NAME = 'metamodels.schema-generator.doctrine';
+
+ /**
+ * {@inheritDoc}
+ */
+ public function process(ContainerBuilder $container): void
+ {
+ $generator = $container->getDefinition(DoctrineSchemaGenerator::class);
+ $generator->setArgument(
+ 0,
+ array_merge($generator->getArgument(0), $this->findAndSortTaggedServices(self::TAG_NAME, $container))
+ );
+ }
+}
diff --git a/src/CoreBundle/DependencyInjection/CompilerPass/CollectFactoriesPass.php b/src/CoreBundle/DependencyInjection/CompilerPass/CollectFactoriesPass.php
index 7996dd543..a43d7453a 100644
--- a/src/CoreBundle/DependencyInjection/CompilerPass/CollectFactoriesPass.php
+++ b/src/CoreBundle/DependencyInjection/CompilerPass/CollectFactoriesPass.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -32,22 +33,22 @@ class CollectFactoriesPass implements CompilerPassInterface
/**
* The tag name to use for attribute factories.
*/
- const TAG_ATTRIBUTE_FACTORY = 'metamodels.attribute_factory';
+ public const TAG_ATTRIBUTE_FACTORY = 'metamodels.attribute_factory';
/**
* The tag name to use for filter factories.
*/
- const TAG_FILTER_FACTORY = 'metamodels.filter_factory';
+ public const TAG_FILTER_FACTORY = 'metamodels.filter_factory';
/**
* The tag name to use for property condition factories.
*/
- const TAG_PROPERTY_CONDITION_FACTORY = 'metamodels.condition_factory';
+ public const TAG_PROPERTY_CONDITION_FACTORY = 'metamodels.condition_factory';
/**
* {@inheritDoc}
*/
- public function process(ContainerBuilder $container)
+ public function process(ContainerBuilder $container): void
{
$this->collectAttributeFactories($container);
$this->collectFilterFactories($container);
@@ -61,10 +62,10 @@ public function process(ContainerBuilder $container)
*
* @return void
*/
- private function collectAttributeFactories($container)
+ private function collectAttributeFactories(ContainerBuilder $container): void
{
$attributeFactory = $container->getDefinition('metamodels.attribute_factory');
- foreach (array_keys($container->findTaggedServiceIds(self::TAG_ATTRIBUTE_FACTORY)) as $factory) {
+ foreach (\array_keys($container->findTaggedServiceIds(self::TAG_ATTRIBUTE_FACTORY)) as $factory) {
$attributeFactory->addMethodCall('addTypeFactory', [new Reference($factory)]);
}
}
@@ -76,10 +77,10 @@ private function collectAttributeFactories($container)
*
* @return void
*/
- private function collectFilterFactories($container)
+ private function collectFilterFactories(ContainerBuilder $container): void
{
$attributeFactory = $container->getDefinition('metamodels.filter_setting_factory');
- foreach (array_keys($container->findTaggedServiceIds(self::TAG_FILTER_FACTORY)) as $factory) {
+ foreach (\array_keys($container->findTaggedServiceIds(self::TAG_FILTER_FACTORY)) as $factory) {
$attributeFactory->addMethodCall('addTypeFactory', [new Reference($factory)]);
}
}
@@ -91,7 +92,7 @@ private function collectFilterFactories($container)
*
* @return void
*/
- private function collectPropertyConditionFactories($container)
+ private function collectPropertyConditionFactories(ContainerBuilder $container): void
{
$factories = $container->getDefinition('metamodels.core_bundle.dc_general.property_condition_factories');
$args = $factories->getArgument(0);
diff --git a/src/CoreBundle/DependencyInjection/CompilerPass/CollectSchemaGeneratorsPass.php b/src/CoreBundle/DependencyInjection/CompilerPass/CollectSchemaGeneratorsPass.php
new file mode 100644
index 000000000..6bf79dfa5
--- /dev/null
+++ b/src/CoreBundle/DependencyInjection/CompilerPass/CollectSchemaGeneratorsPass.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2012-2019 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+declare(strict_types=1);
+
+namespace MetaModels\CoreBundle\DependencyInjection\CompilerPass;
+
+use MetaModels\Schema\SchemaGenerator;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * This pass adds the tagged schema generators.
+ */
+class CollectSchemaGeneratorsPass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ public const TAG_NAME = 'metamodels.schema-generator';
+
+ /**
+ * {@inheritDoc}
+ */
+ public function process(ContainerBuilder $container): void
+ {
+ $generator = $container->getDefinition(SchemaGenerator::class);
+ $generator->setArgument(
+ 0,
+ array_merge($generator->getArgument(0), $this->findAndSortTaggedServices(self::TAG_NAME, $container))
+ );
+ }
+}
diff --git a/src/CoreBundle/DependencyInjection/CompilerPass/CollectSchemaManagersPass.php b/src/CoreBundle/DependencyInjection/CompilerPass/CollectSchemaManagersPass.php
new file mode 100644
index 000000000..e76815c52
--- /dev/null
+++ b/src/CoreBundle/DependencyInjection/CompilerPass/CollectSchemaManagersPass.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2012-2019 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+declare(strict_types=1);
+
+namespace MetaModels\CoreBundle\DependencyInjection\CompilerPass;
+
+use MetaModels\Schema\SchemaManager;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * This pass adds the tagged schema managers.
+ */
+class CollectSchemaManagersPass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ public const TAG_NAME = 'metamodels.schema-manager';
+
+ /**
+ * {@inheritDoc}
+ */
+ public function process(ContainerBuilder $container): void
+ {
+ $generator = $container->getDefinition(SchemaManager::class);
+ $generator->setArgument(
+ 0,
+ array_merge($generator->getArgument(0), $this->findAndSortTaggedServices(self::TAG_NAME, $container))
+ );
+ }
+}
diff --git a/src/CoreBundle/DependencyInjection/CompilerPass/PrepareTranslatorPass.php b/src/CoreBundle/DependencyInjection/CompilerPass/PrepareTranslatorPass.php
new file mode 100644
index 000000000..be83bc0ba
--- /dev/null
+++ b/src/CoreBundle/DependencyInjection/CompilerPass/PrepareTranslatorPass.php
@@ -0,0 +1,47 @@
+
+ * @copyright 2012-2024 The MetaModels team.
+ * @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
+ * @filesource
+ */
+
+declare(strict_types=1);
+
+namespace MetaModels\CoreBundle\DependencyInjection\CompilerPass;
+
+use MetaModels\CoreBundle\Translator\MetaModelTranslatorConfigurator;
+use Symfony\Bundle\FrameworkBundle\Translation\Translator;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+final class PrepareTranslatorPass implements CompilerPassInterface
+{
+ public function process(ContainerBuilder $container): void
+ {
+ $definition = $container->getDefinition('translator.default');
+ if (Translator::class !== $definition->getClass()) {
+ return;
+ }
+ $previousConfigurator = $definition->getConfigurator();
+ $definition->setConfigurator(new Reference(MetaModelTranslatorConfigurator::class));
+ if (null !== $previousConfigurator) {
+ $container->getDefinition(MetaModelTranslatorConfigurator::class)->setArgument(
+ '$previous',
+ $previousConfigurator
+ );
+ }
+ }
+}
diff --git a/src/CoreBundle/DependencyInjection/Configuration.php b/src/CoreBundle/DependencyInjection/Configuration.php
index a72d9b4a4..e040e8daa 100644
--- a/src/CoreBundle/DependencyInjection/Configuration.php
+++ b/src/CoreBundle/DependencyInjection/Configuration.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -22,7 +23,7 @@
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
-use Webmozart\PathUtil\Path;
+use Symfony\Component\Filesystem\Path;
/**
* Adds the Contao configuration structure.
@@ -34,14 +35,14 @@ class Configuration implements ConfigurationInterface
*
* @var bool
*/
- private $debug;
+ private bool $debug;
/**
* The root directory.
*
* @var string
*/
- private $rootDir;
+ private string $rootDir;
/**
* Constructor.
@@ -51,7 +52,7 @@ class Configuration implements ConfigurationInterface
*/
public function __construct($debug, $rootDir)
{
- $this->debug = (bool) $debug;
+ $this->debug = $debug;
$this->rootDir = $rootDir;
}
@@ -59,13 +60,15 @@ public function __construct($debug, $rootDir)
* Generates the configuration tree builder.
*
* @return TreeBuilder
+ *
+ * @psalm-suppress UndefinedMethod
*/
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('metamodels');
+ $treeBuilder = new TreeBuilder('metamodels');
- $rootNode
+ $treeBuilder
+ ->getRootNode()
->children()
->booleanNode('enable_cache')
->defaultValue(!$this->debug)
@@ -77,7 +80,7 @@ public function getConfigTreeBuilder()
->cannotBeEmpty()
->defaultValue($this->resolvePath($this->rootDir . '/assets/metamodels'))
->validate()
- ->always(function ($value) {
+ ->always(function (string $value): string {
return $this->resolvePath($value);
})
->end()
@@ -98,12 +101,12 @@ public function getConfigTreeBuilder()
*
* @return string
*/
- private function resolvePath($value)
+ private function resolvePath(string $value): string
{
$path = Path::canonicalize($value);
if ('\\' === DIRECTORY_SEPARATOR) {
- $path = str_replace('/', '\\', $path);
+ $path = \str_replace('/', '\\', $path);
}
return $path;
diff --git a/src/CoreBundle/DependencyInjection/IdProvidingServiceLocator.php b/src/CoreBundle/DependencyInjection/IdProvidingServiceLocator.php
index 767041299..c1dc2d39c 100644
--- a/src/CoreBundle/DependencyInjection/IdProvidingServiceLocator.php
+++ b/src/CoreBundle/DependencyInjection/IdProvidingServiceLocator.php
@@ -17,7 +17,7 @@
* @filesource
*/
-declare(strict_types = 1);
+declare(strict_types=1);
namespace MetaModels\CoreBundle\DependencyInjection;
diff --git a/src/CoreBundle/DependencyInjection/MetaModelsCoreExtension.php b/src/CoreBundle/DependencyInjection/MetaModelsCoreExtension.php
index cddb6d7ee..1aa37c4f7 100644
--- a/src/CoreBundle/DependencyInjection/MetaModelsCoreExtension.php
+++ b/src/CoreBundle/DependencyInjection/MetaModelsCoreExtension.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -16,20 +16,22 @@
* @author Sven Baumann
* @author Ingolf Steinhardt
* @author Richard Henkenjohann
- * @copyright 2012-2020 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\DependencyInjection;
-use Doctrine\Common\Cache\ArrayCache;
use MetaModels\CoreBundle\Migration\TableCollationMigration;
+use MetaModels\Filter\FilterUrlBuilder;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
+use Symfony\Component\DependencyInjection\Reference;
/**
* This is the class that loads and manages the bundle configuration
@@ -41,7 +43,7 @@ class MetaModelsCoreExtension extends Extension implements PrependExtensionInter
*
* @var array
*/
- private $defaultTableOptions;
+ private array $defaultTableOptions = [];
/**
* The configuration files.
@@ -86,14 +88,16 @@ public function prepend(ContainerBuilder $container): void
/**
* {@inheritDoc}
*/
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
foreach (self::$files as $file) {
$loader->load($file);
}
- $config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);
+ $configuration = $this->getConfiguration($configs, $container);
+ assert($configuration instanceof Configuration);
+ $config = $this->processConfiguration($configuration, $configs);
$this->buildCacheService($container, $config);
$container->setParameter('metamodels.resource_dir', __DIR__ . '/../Resources');
@@ -102,6 +106,11 @@ public function load(array $configs, ContainerBuilder $container)
$container->getDefinition(TableCollationMigration::class)
->setArgument('$defaultTableOptions', $this->defaultTableOptions);
+
+ if ((bool) $container->getParameter('contao.legacy_routing')) {
+ $container->getDefinition(FilterUrlBuilder::class)
+ ->setArgument(0, new Reference('contao.routing.url_generator'));
+ }
}
/**
@@ -109,10 +118,9 @@ public function load(array $configs, ContainerBuilder $container)
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
- return new Configuration(
- $container->getParameter('kernel.debug'),
- $container->getParameter('kernel.project_dir')
- );
+ $projectDir = $container->getParameter('kernel.project_dir');
+ assert(\is_string($projectDir));
+ return new Configuration((bool) $container->getParameter('kernel.debug'), $projectDir);
}
/**
@@ -123,12 +131,12 @@ public function getConfiguration(array $config, ContainerBuilder $container)
*
* @return void
*/
- private function buildCacheService(ContainerBuilder $container, array $config)
+ private function buildCacheService(ContainerBuilder $container, array $config): void
{
- // if cache disabled, swap it out with the dummy cache.
+ // If cache disabled, swap it out with the dummy cache.
if (!$config['enable_cache']) {
- $cache = $container->getDefinition('metamodels.cache');
- $cache->setClass(ArrayCache::class);
+ $cache = $container->getDefinition('metamodels.cache_internal');
+ $cache->setClass(ArrayAdapter::class);
$cache->setArguments([]);
$container->setParameter('metamodels.cache_dir', null);
return;
@@ -138,7 +146,7 @@ private function buildCacheService(ContainerBuilder $container, array $config)
}
/**
- * Collect the default table options from the dcotrine extension.
+ * Collect the default table options from the doctrine extension.
*
* @param ContainerBuilder $container The container builder.
*
diff --git a/src/CoreBundle/EventListener/AttributeAddingListener.php b/src/CoreBundle/EventListener/AttributeAddingListener.php
index 1fc3ff400..9cd90fd34 100644
--- a/src/CoreBundle/EventListener/AttributeAddingListener.php
+++ b/src/CoreBundle/EventListener/AttributeAddingListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -33,7 +34,7 @@ class AttributeAddingListener
*
* @var IAttributeFactory
*/
- private $attributeFactory;
+ private IAttributeFactory $attributeFactory;
/**
* Create a new instance.
diff --git a/src/CoreBundle/EventListener/BackendNavigationListener.php b/src/CoreBundle/EventListener/BackendNavigationListener.php
index 200b150a9..93c240aeb 100644
--- a/src/CoreBundle/EventListener/BackendNavigationListener.php
+++ b/src/CoreBundle/EventListener/BackendNavigationListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,25 +14,34 @@
* @author Christian Schiffler
* @author Richard Henkenjohann
* @author Ingolf Steinhardt
- * @copyright 2012-2020 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener;
-use Contao\BackendUser;
use Contao\CoreBundle\Event\MenuEvent;
-use Contao\StringUtil;
+use Knp\Menu\FactoryInterface;
+use Knp\Menu\ItemInterface;
use MetaModels\ViewCombination\ViewCombination;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
+use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
-use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+use function array_key_exists;
+use function array_map;
+use function in_array;
+use function is_array;
/**
* This registers the backend navigation of MetaModels.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class BackendNavigationListener
{
@@ -48,28 +57,35 @@ class BackendNavigationListener
*
* @var TranslatorInterface
*/
- private $translator;
+ private TranslatorInterface $translator;
/**
* The translator in use.
*
* @var ViewCombination
*/
- private $viewCombination;
+ private ViewCombination $viewCombination;
/**
* The token storage.
*
* @var TokenStorageInterface
*/
- private $tokenStorage;
+ private TokenStorageInterface $tokenStorage;
/**
* The router.
*
* @var RouterInterface
*/
- private $router;
+ private RouterInterface $router;
+
+ /**
+ * The session.
+ *
+ * @var Session
+ */
+ private Session $session;
/**
* Create a new instance.
@@ -79,19 +95,22 @@ class BackendNavigationListener
* @param ViewCombination $viewCombination The view combination.
* @param TokenStorageInterface $tokenStorage The token storage.
* @param RouterInterface $router The router.
+ * @param Session $session The session.
*/
public function __construct(
TranslatorInterface $translator,
RequestStack $requestStack,
ViewCombination $viewCombination,
TokenStorageInterface $tokenStorage,
- RouterInterface $router
+ RouterInterface $router,
+ Session $session,
) {
$this->requestStack = $requestStack;
$this->translator = $translator;
$this->viewCombination = $viewCombination;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
+ $this->session = $session;
}
/**
@@ -113,40 +132,38 @@ public function __invoke(MenuEvent $event): void
return;
}
- if (null === $request = $this->requestStack->getCurrentRequest()) {
+ if (null === ($request = $this->requestStack->getCurrentRequest())) {
+ return;
+ }
+
+ if (null === ($user = $this->tokenStorage->getToken())) {
return;
}
$this->addBackendCss();
- if (null !== ($user = $this->tokenStorage->getToken())) {
- $userRights = $this->extractUserRights($user);
- }
+ $isAdmin = in_array('ROLE_ADMIN', $user->getRoleNames(), true);
- $isAdmin = \in_array('ROLE_ADMIN', $user->getRoleNames(), true);
+ $metaModelsNode = $this->getRootNode($tree, $factory);
- $metaModelsNode = $tree->getChild('metamodels');
- if (null !== $metaModelsNode && ($isAdmin || isset($userRights['support_metamodels']))) {
- $node = $factory
- ->createItem('support_screen')
- ->setUri($this->router->generate('metamodels.support_screen'))
- ->setLabel($this->translator->trans('MOD.support_metamodels.0', [], 'contao_modules'))
- ->setLinkAttribute('title', $this->translator->trans('MOD.support_metamodels.1', [], 'contao_modules'))
- ->setLinkAttribute('class', 'support_screen')
- ->setCurrent('metamodels.support_screen' === $request->get('_route'));
+ $names = array_map(static fn(ItemInterface $item): string => $item->getName(), $metaModelsNode->getChildren());
- $metaModelsNode->addChild($node);
+ // Show MetaModels config only for Admins.
+ if ($isAdmin) {
+ $metaModelsNode->addChild($configNode = $this->buildConfigNode($factory, $request));
+ array_unshift($names, $configNode->getName());
}
- $locale = $request->getLocale();
- foreach ($this->viewCombination->getStandalone() as $metaModelName => $screen) {
- $moduleName = 'metamodel_' . $metaModelName;
- if (!$isAdmin && !isset($userRights[$moduleName])) {
- continue;
- }
+ $metaModelsNode->reorderChildren($names);
+
+ $currentMetaModel = '';
+ if ($request->attributes->get('_route') === 'metamodels.metamodel') {
+ $currentMetaModel = (string) (($request->attributes->get('_route_params', []))['tableName'] ?? '');
+ }
- $sectionNode = $tree->getChild($screen['meta']['backendsection']);
- if (null === $sectionNode) {
+ foreach ($this->viewCombination->getStandalone() as $metaModelName => $screen) {
+ if (null === $sectionNode = $this->getSectionNode($factory, $tree, $screen['meta']['backendsection'])) {
+ // Rien ne vas plus.
continue;
}
@@ -154,69 +171,232 @@ public function __invoke(MenuEvent $event): void
$node = $factory
->createItem($item)
- ->setUri($this->router->generate('contao_backend', ['do' => $item]))
- ->setLabel($this->extractLanguageValue($screen['label'], $locale))
- ->setLinkAttribute('title', $this->extractLanguageValue($screen['description'], $locale))
+ ->setUri($this->router->generate('metamodels.metamodel', ['tableName' => $metaModelName]))
+ ->setLabel('inputscreen.' . $screen['meta']['id'] . '.menu.label')
+ ->setExtra('translation_domain', $metaModelName)
+ ->setLinkAttribute(
+ 'title',
+ $this->translator->trans(
+ 'inputscreen.' . $screen['meta']['id'] . '.menu.description',
+ [],
+ $metaModelName
+ )
+ )
->setLinkAttribute('class', $item)
- ->setCurrent($request->get('_route') === 'contao_backend' && $request->query->get('do') === $item);
+ ->setCurrent($currentMetaModel === $metaModelName);
$sectionNode->addChild($node);
}
+
+ if ($isAdmin) {
+ $node = $factory
+ ->createItem('support_screen')
+ ->setUri($this->router->generate('metamodels.support_screen'))
+ ->setLabel('menu.label')
+ ->setExtra('translation_domain', 'metamodels_support')
+ ->setLinkAttribute('title', $this->translator->trans('menu.description', [], 'metamodels_support'))
+ ->setLinkAttribute('class', 'support_screen')
+ ->setCurrent('metamodels.support_screen' === $request->attributes->get('_route'));
+
+ $metaModelsNode->addChild($node);
+ }
}
/**
- * Extract the permissions from the Contao backend user.
+ * Add the CSS files for the backend.
*
- * @param TokenInterface $token The token.
+ * @return void
*
- * @return array
+ * @SuppressWarnings(PHPMD.Superglobals)
+ * @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- private function extractUserRights(TokenInterface $token): array
+ private function addBackendCss(): void
{
- $beUser = $token->getUser();
- if (!($beUser instanceof BackendUser)) {
- return [];
- }
+ // BE group icon.
+ $GLOBALS['TL_CSS']['metamodels'] = 'bundles/metamodelscore/css/be_logo_svg.css';
+ }
- $allowedModules = $beUser->modules;
- switch (true) {
- case \is_string($allowedModules):
- $allowedModules = StringUtil::deserialize($allowedModules, true);
- break;
- case null === $allowedModules:
- $allowedModules = [];
- break;
- default:
+ /**
+ * Get root node.
+ *
+ * @param ItemInterface $tree The tree.
+ * @param FactoryInterface $factory The factory.
+ *
+ * @return ItemInterface
+ */
+ private function getRootNode(ItemInterface $tree, FactoryInterface $factory): ItemInterface
+ {
+ $names =
+ $this->getChildNamesFromTree($tree);
+ $insertPos = (int) array_search('accounts', $names, true) + 1;
+ $metaModelsNode = $tree->getChild('metamodels');
+ if (null === $metaModelsNode) {
+ $metaModelsNode = $factory->createItem('metamodels');
+ $tree->addChild($metaModelsNode);
}
- return array_flip($allowedModules);
+ $metaModelsNode
+ ->setLabel('menuGroup.metamodels.label')
+ ->setExtra('translation_domain', 'metamodels_navigation');
+
+ $this->updateCollapsedState($metaModelsNode);
+
+ // Resort if already existing.
+ $names = array_values(array_filter($names, static fn(string $name): bool => 'metamodels' !== $name));
+
+ array_splice($names, $insertPos, 0, 'metamodels');
+ $tree->reorderChildren($names);
+
+ return $metaModelsNode;
}
/**
- * Extract the language value.
+ * Generate build config.
*
- * @param string[] $values The values.
+ * @param FactoryInterface $factory The factory.
+ * @param Request $request The request.
*
- * @param string $locale The current locale.
- *
- * @return string
+ * @return ItemInterface
*/
- private function extractLanguageValue($values, $locale): string
+ private function buildConfigNode(FactoryInterface $factory, Request $request): ItemInterface
{
- return html_entity_decode(($values[$locale] ?? $values['']));
+ $configNode = $factory->createItem('metamodels');
+
+ $configNode
+ ->setUri($this->router->generate('metamodels.configuration'))
+ ->setLabel('menu.metamodels.label')
+ ->setExtra('translation_domain', 'metamodels_navigation')
+ ->setLinkAttribute('title', $this->translator->trans('menu.metamodels.title', [], 'metamodels_navigation'))
+ ->setLinkAttribute('class', 'metamodel_config')
+ ->setCurrent('metamodels.configuration' === $request->attributes->get('_route'));
+
+ return $configNode;
}
/**
- * Add the CSS files for the backend.
+ * Update collapsed state of navigation groups.
+ *
+ * @param ItemInterface $metaModelsNode
*
* @return void
+ */
+ private function updateCollapsedState(ItemInterface $metaModelsNode): void
+ {
+ $nodeName = $metaModelsNode->getName();
+ $sessionBag = $this->session->getBag('contao_backend');
+ $status = ($sessionBag instanceof AttributeBagInterface) ? $sessionBag->get('backend_modules') : [];
+ $isCollapsed = ($status[$nodeName] ?? 1) < 1;
+ $path = $this->router->generate('contao_backend');
+
+ $metaModelsNode
+ ->setLinkAttribute('class', 'group-' . $nodeName)
+ ->setLinkAttribute(
+ 'onclick',
+ "return AjaxRequest.toggleNavigation(this, '" . $nodeName . "', '" . $path . "')"
+ )
+ ->setLinkAttribute('aria-controls', $nodeName)
+ ->setChildrenAttribute('id', $nodeName)
+ ->setLinkAttribute(
+ 'title',
+ $this->translator->trans('MSC.' . ($isCollapsed ? 'expand' : 'collapse') . 'Node', [], 'contao_default')
+ )
+ ->setLinkAttribute('aria-expanded', $isCollapsed ? 'false' : 'true');
+
+ if ($isCollapsed) {
+ $metaModelsNode->setAttribute('class', 'collapsed');
+ }
+
+ $request = $this->requestStack->getCurrentRequest();
+ if (null === $request) {
+ return;
+ }
+
+ $uri = $this->router->generate(
+ 'contao_backend',
+ [
+ 'do' => $request->query->get('do'),
+ 'mtg' => $nodeName,
+ 'ref' => $request->attributes->get('_contao_referer_id')
+ ]
+ );
+
+ $metaModelsNode->setUri($uri);
+ }
+
+ /**
+ * Get section node.
+ *
+ * @param FactoryInterface $factory The factory.
+ * @param ItemInterface $tree The item interface.
+ * @param string $backendSection The backend section.
+ *
+ * @return ItemInterface|null
*
* @SuppressWarnings(PHPMD.Superglobals)
- * @SuppressWarnings(PHPMD.CamelCaseVariableName)
*/
- private function addBackendCss(): void
+ private function getSectionNode(
+ FactoryInterface $factory,
+ ItemInterface $tree,
+ string $backendSection
+ ): ?ItemInterface {
+ if (null !== $sectionNode = $tree->getChild($backendSection)) {
+ return $sectionNode;
+ }
+
+ // Somehow it disappeared - try to generate it via BE_MOD.
+ if (!array_key_exists($backendSection, $GLOBALS['BE_MOD'])) {
+ return null;
+ }
+ $navigation = array_keys($GLOBALS['BE_MOD']);
+ // Keep child names before adding the new child.
+ $namesInMenu = $this->getChildNamesFromTree($tree);
+ $sectionNode = $factory->createItem($backendSection);
+ $tree->addChild($sectionNode);
+ $sectionNode
+ ->setLabel($this->getLabelForSection($backendSection))
+ ->setExtra('translation_domain', false);
+
+ $this->updateCollapsedState($sectionNode);
+
+ // Search the position in the already existing menu by starting at offset in BE_MOD and walking up to the start.
+ $start = (int) array_search($backendSection, $navigation, true);
+ while (0 <= --$start) {
+ /** @psalm-suppress InvalidArrayOffset */
+ if (in_array($navigation[$start], $namesInMenu, true)) {
+ array_splice($namesInMenu, $start + 1, 0, [$backendSection]);
+ break;
+ }
+ }
+ // If we did not find a position in existing menu, append at the end.
+ if (!in_array($backendSection, $namesInMenu)) {
+ $namesInMenu[] = $backendSection;
+ }
+ $tree->reorderChildren($namesInMenu);
+
+ return $sectionNode;
+ }
+
+ /**
+ * @param ItemInterface $tree
+ *
+ * @return list
+ */
+ private function getChildNamesFromTree(ItemInterface $tree): array
{
- // BE group icon.
- $GLOBALS['TL_CSS']['metamodels'] = 'bundles/metamodelscore/css/be_logo_svg.css';
+ return array_values(
+ array_map(static fn(ItemInterface $item): string => $item->getName(), $tree->getChildren())
+ );
+ }
+
+ /** @SuppressWarnings(PHPMD.Superglobals) */
+ public function getLabelForSection(string $backendSection): string
+ {
+ /** @var null|list|string $langValue */
+ $langValue = $GLOBALS['TL_LANG']['MOD'][$backendSection] ?? null;
+ if (is_array($langValue)) {
+ return $langValue[0] ?? $backendSection;
+ }
+
+ return $langValue ?? $backendSection;
}
}
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/AbstractBreadcrumbListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/AbstractBreadcrumbListener.php
index 1d18c6015..2dcc30a56 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/AbstractBreadcrumbListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/AbstractBreadcrumbListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -23,6 +24,7 @@
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
+use ContaoCommunityAlliance\DcGeneral\InputProviderInterface;
/**
* This class renders various breadcrumbs.
@@ -34,14 +36,14 @@ abstract class AbstractBreadcrumbListener
*
* @var BreadcrumbStoreFactory
*/
- private $storeFactory;
+ private BreadcrumbStoreFactory $storeFactory;
/**
* The parent element renderer.
*
- * @var AbstractBreadcrumbListener
+ * @var AbstractBreadcrumbListener|null
*/
- private $parent;
+ private ?AbstractBreadcrumbListener $parent;
/**
* Create a new instance.
@@ -90,7 +92,6 @@ abstract protected function wantToHandle(GetBreadcrumbEvent $event);
* Perform the bread crumb generating.
*
* @param EnvironmentInterface $environment The environment in use.
- *
* @param BreadcrumbStore $elements The elements generated so far.
*
* @return void
@@ -106,15 +107,17 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
* Extract the id value from the serialized parameter with the given name.
*
* @param EnvironmentInterface $environment The environment.
- *
* @param string $parameterName The parameter name containing the id.
*
- * @return int
+ * @return string
*/
protected function extractIdFrom(EnvironmentInterface $environment, $parameterName = 'pid')
{
- $parameter = $environment->getInputProvider()->getParameter($parameterName);
+ $inputProvider = $environment->getInputProvider();
+ assert($inputProvider instanceof InputProviderInterface);
+
+ $parameter = $inputProvider->getParameter($parameterName);
- return ModelId::fromSerialized($parameter)->getId();
+ return (string) ModelId::fromSerialized($parameter)->getId();
}
}
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbAttributeListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbAttributeListener.php
index 13e789233..33a12e5c3 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbAttributeListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbAttributeListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_attribute.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbAttributeListener extends AbstractBreadcrumbListener
{
@@ -37,7 +42,10 @@ class BreadcrumbAttributeListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_attribute' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_attribute' === $dataDefinition->getName();
}
/**
@@ -61,11 +69,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $modelId = $elements->getId('tl_metamodel');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_attribute'),
- $this->getMetaModel($elements->getId('tl_metamodel'))->getName()
+ (null !== $modelId) ? $this->getMetaModel($modelId)->getName() : ''
),
'bundles/metamodelscore/images/icons/fields.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaCombineListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaCombineListener.php
index 8d7cc11fe..6038beb91 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaCombineListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaCombineListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_dcacombine.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbDcaCombineListener extends AbstractBreadcrumbListener
{
@@ -37,7 +42,10 @@ class BreadcrumbDcaCombineListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_dca_combine' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_dca_combine' === $dataDefinition->getName();
}
/**
@@ -61,11 +69,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $modelId = $elements->getId('tl_metamodel');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_dca_combine'),
- $this->getMetaModel($elements->getId('tl_metamodel'))->getName()
+ (null !== $modelId) ? $this->getMetaModel($modelId)->getName() : ''
),
'bundles/metamodelscore/images/icons/dca_combine.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaListener.php
index e87abbba3..9b6946407 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_dca.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbDcaListener extends AbstractBreadcrumbListener
{
@@ -38,7 +43,10 @@ class BreadcrumbDcaListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_dca' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_dca' === $dataDefinition->getName();
}
/**
@@ -52,7 +60,7 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
} else {
$elements->setId(
'tl_metamodel',
- $this->getRow($elements->getId('tl_metamodel_dca'), 'tl_metamodel_dca')->pid
+ $this->getRow($elements->getId('tl_metamodel_dca') ?? '', 'tl_metamodel_dca')->pid
);
}
}
@@ -69,11 +77,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $modelId = $elements->getId('tl_metamodel');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_dca'),
- $this->getMetaModel($elements->getId('tl_metamodel'))->getName()
+ (null !== $modelId) ? $this->getMetaModel($modelId)->getName() : ''
),
'bundles/metamodelscore/images/icons/dca.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingConditionListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingConditionListener.php
index e5d05c2f2..bd38c2c46 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingConditionListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingConditionListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -24,11 +24,16 @@
use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
+use MetaModels\Helper\LocaleUtil;
+use MetaModels\IMetaModel;
/**
* Generate a breadcrumb for table tl_metamodel_dcasetting_condition.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbDcaSettingConditionListener extends AbstractBreadcrumbListener
{
@@ -40,7 +45,10 @@ class BreadcrumbDcaSettingConditionListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_dcasetting_condition' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_dcasetting_condition' === $dataDefinition->getName();
}
/**
@@ -67,11 +75,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $dcaSettingId = $elements->getId('tl_metamodel_dcasetting');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_dcasetting_condition'),
- $this->getConditionAttribute($elements->getId('tl_metamodel_dcasetting'))
+ (null !== $dcaSettingId) ? $this->getConditionAttribute($dcaSettingId) : ''
),
'bundles/metamodelscore/images/icons/dca_condition.png'
);
@@ -91,16 +100,19 @@ private function getConditionAttribute($settingId)
{
$setting = $this->getRow($settingId, 'tl_metamodel_dcasetting');
- if ($setting->dcatype == 'attribute') {
- $attribute = (object) $this->getRow($setting->attr_id, 'tl_metamodel_attribute');
+ if ($setting->dcatype === 'attribute') {
+ $attribute = $this->getRow($setting->attr_id, 'tl_metamodel_attribute');
$metaModelName = $this->factory->translateIdToMetaModelName($attribute->pid);
- $attribute = $this->factory->getMetaModel($metaModelName)->getAttributeById($attribute->id);
+ $metaModel = $this->factory->getMetaModel($metaModelName);
+ assert($metaModel instanceof IMetaModel);
+ $attribute = $metaModel->getAttributeById((int) $attribute->id);
if ($attribute) {
return $attribute->getName();
}
} else {
$title = StringUtil::deserialize($setting->legendtitle, true);
- return ($title[\str_replace('-', '_', $GLOBALS['TL_LANGUAGE'])] ?? current($title));
+ // @deprecated usage of TL_LANGUAGE - remove for Contao 5.0.
+ return ($title[LocaleUtil::formatAsLocale($GLOBALS['TL_LANGUAGE'])] ?? current($title));
}
return 'unknown ' . $setting->dcatype;
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingListener.php
index 7e88c2e57..b6ae9e430 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSettingListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_dcasetting.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbDcaSettingListener extends AbstractBreadcrumbListener
{
@@ -37,7 +42,10 @@ class BreadcrumbDcaSettingListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_dcasetting' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_dcasetting' === $dataDefinition->getName();
}
/**
@@ -51,7 +59,7 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
} else {
$elements->setId(
'tl_metamodel_dca',
- $this->getRow($elements->getId('tl_metamodel_dcasetting'), 'tl_metamodel_dcasetting')->pid
+ $this->getRow($elements->getId('tl_metamodel_dcasetting') ?? '', 'tl_metamodel_dcasetting')->pid
);
}
}
@@ -69,11 +77,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $dcaId = $elements->getId('tl_metamodel_dca');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_dcasetting'),
- $this->getRow($elements->getId('tl_metamodel_dca'), 'tl_metamodel_dca')->name
+ (null !== $dcaId) ? $this->getRow($dcaId, 'tl_metamodel_dca')->name : ''
),
'bundles/metamodelscore/images/icons/dca_setting.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSortGroupListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSortGroupListener.php
index 6dd33b7df..90b2896c5 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSortGroupListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbDcaSortGroupListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_dca_sortgroup.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbDcaSortGroupListener extends AbstractBreadcrumbListener
{
@@ -37,7 +42,10 @@ class BreadcrumbDcaSortGroupListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_dca_sortgroup' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_dca_sortgroup' === $dataDefinition->getName();
}
/**
@@ -62,11 +70,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $dcaId = $elements->getId('tl_metamodel_dca');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_dca_sortgroup'),
- $this->getRow($elements->getId('tl_metamodel_dca'), 'tl_metamodel_dca')->name
+ (null !== $dcaId) ? $this->getRow($dcaId, 'tl_metamodel_dca')->name : ''
),
'bundles/metamodelscore/images/icons/dca_groupsortsettings.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterListener.php
index 2f5a8c96c..b52a93c5e 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_filter.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbFilterListener extends AbstractBreadcrumbListener
{
@@ -38,7 +43,10 @@ class BreadcrumbFilterListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_filter' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_filter' === $dataDefinition->getName();
}
/**
@@ -52,7 +60,7 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
} else {
$elements->setId(
'tl_metamodel',
- $this->getRow($elements->getId('tl_metamodel_filter'), 'tl_metamodel_filter')->pid
+ $this->getRow($elements->getId('tl_metamodel_filter') ?? '', 'tl_metamodel_filter')->pid
);
}
}
@@ -69,11 +77,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $modelId = $elements->getId('tl_metamodel');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_filter'),
- $this->getMetaModel($elements->getId('tl_metamodel'))->getName()
+ (null !== $modelId) ? $this->getMetaModel($modelId)->getName() : ''
),
'bundles/metamodelscore/images/icons/filter.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterSettingListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterSettingListener.php
index 4ae117bcf..35cfcb1bb 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterSettingListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbFilterSettingListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_dcasetting.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbFilterSettingListener extends AbstractBreadcrumbListener
{
@@ -37,7 +42,10 @@ class BreadcrumbFilterSettingListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_filtersetting' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_filtersetting' === $dataDefinition->getName();
}
/**
@@ -51,7 +59,10 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
} else {
$elements->setId(
'tl_metamodel_filter',
- $this->getRow($elements->getId('tl_metamodel_filtersetting'), 'tl_metamodel_filtersetting')->pid
+ $this->getRow(
+ $elements->getId('tl_metamodel_filtersetting') ?? '',
+ 'tl_metamodel_filtersetting'
+ )->pid
);
}
}
@@ -69,11 +80,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $filterId = $elements->getId('tl_metamodel_filter');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_filtersetting'),
- $this->getRow($elements->getId('tl_metamodel_filter'), 'tl_metamodel_filter')->name
+ (null !== $filterId) ? $this->getRow($filterId, 'tl_metamodel_filter')->name : ''
),
'bundles/metamodelscore/images/icons/filter_setting.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbMetaModelListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbMetaModelListener.php
index 75c356ec3..a996ab0d7 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbMetaModelListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbMetaModelListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -21,10 +22,13 @@
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
/**
* Generate a breadcrumb for table tl_metamodel.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbMetaModelListener extends AbstractBreadcrumbListener
{
@@ -35,7 +39,10 @@ class BreadcrumbMetaModelListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel' === $dataDefinition->getName();
}
/**
@@ -44,7 +51,7 @@ protected function wantToHandle(GetBreadcrumbEvent $event)
protected function getBreadcrumbElements(EnvironmentInterface $environment, BreadcrumbStore $elements)
{
$elements->push(
- 'contao/main.php?do=metamodels',
+ 'contao/metamodels',
'tl_metamodel',
'bundles/metamodelscore/images/backend/logo.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingListener.php
index 3d2a6a105..27afffca9 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_rendersetting.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbRenderSettingListener extends AbstractBreadcrumbListener
{
@@ -37,7 +42,10 @@ class BreadcrumbRenderSettingListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_rendersetting' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_rendersetting' === $dataDefinition->getName();
}
/**
@@ -64,11 +72,15 @@ public function getBreadcrumbElements(EnvironmentInterface $environment, Breadcr
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $renderSettingsId = $elements->getId('tl_metamodel_rendersettings');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_rendersetting'),
- $this->getRow($elements->getId('tl_metamodel_rendersettings'), 'tl_metamodel_rendersettings')->name
+ (null !== $renderSettingsId) ? $this->getRow(
+ $renderSettingsId,
+ 'tl_metamodel_rendersettings'
+ )->name : ''
),
'bundles/metamodelscore/images/icons/rendersetting.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingsListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingsListener.php
index 91d70b43c..923f88508 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingsListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbRenderSettingsListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_rendersettings.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbRenderSettingsListener extends AbstractBreadcrumbListener
{
@@ -38,7 +43,10 @@ class BreadcrumbRenderSettingsListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_rendersettings' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_rendersettings' === $dataDefinition->getName();
}
/**
@@ -52,7 +60,10 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
} else {
$elements->setId(
'tl_metamodel',
- $this->getRow($elements->getId('tl_metamodel_rendersettings'), 'tl_metamodel_rendersettings')->pid
+ $this->getRow(
+ $elements->getId('tl_metamodel_rendersettings') ?? '',
+ 'tl_metamodel_rendersettings'
+ )->pid
);
}
}
@@ -69,11 +80,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $modelId = $elements->getId('tl_metamodel');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_rendersettings'),
- $this->getMetaModel($elements->getId('tl_metamodel'))->getName()
+ (null !== $modelId) ? $this->getMetaModel($modelId)->getName() : ''
),
'bundles/metamodelscore/images/icons/rendersettings.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbSearchablePagesListener.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbSearchablePagesListener.php
index 0b83e1d9c..4d5dd3784 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbSearchablePagesListener.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbSearchablePagesListener.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,20 +13,25 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;
+use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
+use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
/**
* Generate a breadcrumb for table tl_metamodel_searchable_pages.
+ *
+ * @psalm-suppress PropertyNotSetInConstructor
*/
class BreadcrumbSearchablePagesListener extends AbstractBreadcrumbListener
{
@@ -38,7 +43,10 @@ class BreadcrumbSearchablePagesListener extends AbstractBreadcrumbListener
*/
protected function wantToHandle(GetBreadcrumbEvent $event)
{
- return 'tl_metamodel_searchable_pages' === $event->getEnvironment()->getDataDefinition()->getName();
+ $dataDefinition = $event->getEnvironment()->getDataDefinition();
+ assert($dataDefinition instanceof ContainerInterface);
+
+ return 'tl_metamodel_searchable_pages' === $dataDefinition->getName();
}
/**
@@ -53,7 +61,7 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
$elements->setId(
'tl_metamodel',
$this->getRow(
- $elements->getId('tl_metamodel_searchable_pages'),
+ $elements->getId('tl_metamodel_searchable_pages') ?? '',
'tl_metamodel_searchable_pages'
)->pid
);
@@ -75,11 +83,12 @@ protected function getBreadcrumbElements(EnvironmentInterface $environment, Brea
->unsetQueryParameter('act')
->unsetQueryParameter('id');
+ $modelId = $elements->getId('tl_metamodel');
$elements->push(
- ampersand($builder->getUrl()),
- sprintf(
+ StringUtil::ampersand($builder->getUrl()),
+ \sprintf(
$elements->getLabel('tl_metamodel_searchable_pages'),
- $this->getMetaModel($elements->getId('tl_metamodel'))->getName()
+ (null !== $modelId) ? $this->getMetaModel($modelId)->getName() : ''
),
'bundles/metamodelscore/images/icons/searchable_pages.png'
);
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStore.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStore.php
index 3040ff26a..2e73cab3e 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStore.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStore.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -22,7 +23,12 @@
use Contao\StringUtil;
use MetaModels\CoreBundle\Assets\IconBuilder;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+use function array_key_exists;
+use function str_replace;
+use function str_starts_with;
+use function ucfirst;
/**
* The breadcrumb store.
@@ -34,35 +40,35 @@ class BreadcrumbStore
*
* @var array
*/
- private $elements = [];
+ private array $elements = [];
/**
* The icon builder.
*
* @var IconBuilder
*/
- private $iconBuilder;
+ private IconBuilder $iconBuilder;
/**
* The translator.
*
* @var TranslatorInterface
*/
- private $translator;
+ private TranslatorInterface $translator;
/**
* List of "current" ids.
*
* @var string[]
*/
- private $idList = [];
+ private array $idList = [];
/**
* The current URI.
*
* @var string
*/
- private $uri;
+ private string $uri;
/**
* Create a new instance.
@@ -144,19 +150,19 @@ public function getUri(): string
}
/**
- * Get for a table the human readable name or a fallback.
+ * Get for a table the human-readable name or a fallback.
*
* @param string $table Name of table.
*
- * @return string The human readable name.
+ * @return string The human-readable name.
*/
public function getLabel($table): string
{
- if (strpos($table, 'tl_') !== 0) {
+ if (!str_starts_with($table, 'tl_')) {
return $table;
}
$shortTable = str_replace('tl_', '', $table);
- $label = $this->translator->trans('BRD.' . $shortTable, [], 'contao_default');
+ $label = $this->translator->trans($shortTable, [], 'metamodels_navigation');
if ($label === $shortTable) {
$shortTable = str_replace('tl_metamodel_', '', $table);
return ucfirst($shortTable) . ' %s';
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStoreFactory.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStoreFactory.php
index d82f43e66..86cc1de55 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStoreFactory.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/BreadcrumbStoreFactory.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2022 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2022 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -22,7 +23,7 @@
use MetaModels\CoreBundle\Assets\IconBuilder;
use Symfony\Component\HttpFoundation\RequestStack;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
/**
* This class creates an instance of a breadcrumb store.
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/ConnectionTrait.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/ConnectionTrait.php
index 9ad8b6788..88db17130 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/ConnectionTrait.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/ConnectionTrait.php
@@ -65,7 +65,7 @@ private function getRow($rowId, $table)
->where('t.id=:id')
->setParameter('id', $rowId)
->setMaxResults(1)
- ->execute()
- ->fetch(\PDO::FETCH_ASSOC);
+ ->executeQuery()
+ ->fetchAssociative();
}
}
diff --git a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/GetMetaModelTrait.php b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/GetMetaModelTrait.php
index 4d7e52c5e..4ea5f68aa 100644
--- a/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/GetMetaModelTrait.php
+++ b/src/CoreBundle/EventListener/DcGeneral/Breadcrumb/GetMetaModelTrait.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,8 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -33,7 +34,7 @@ trait GetMetaModelTrait
*
* @var IFactory
*/
- private $factory;
+ private IFactory $factory;
/**
* Retrieve the MetaModel instance.
@@ -46,12 +47,13 @@ trait GetMetaModelTrait
*/
protected function getMetaModel($metaModelId)
{
- if (null === $this->factory) {
- throw new \RuntimeException('No factory set.');
- }
-
$metaModelName = $this->factory->translateIdToMetaModelName($metaModelId);
- $metaModel = $this->factory->getMetaModel($metaModelName);
+
+ $metaModel = $this->factory->getMetaModel($metaModelName);
+
+ if (null === $metaModel) {
+ throw new \RuntimeException('MetaModel not found');
+ }
return $metaModel;
}
@@ -59,7 +61,7 @@ protected function getMetaModel($metaModelId)
/**
* {@inheritDoc}
*/
- public function setMetaModelFactory(IFactory $factory)
+ public function setMetaModelFactory(IFactory $factory): void
{
$this->factory = $factory;
}
diff --git a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/AbstractConditionBuilder.php b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/AbstractConditionBuilder.php
index 89050be84..6026d0fe1 100644
--- a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/AbstractConditionBuilder.php
+++ b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/AbstractConditionBuilder.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,16 +13,14 @@
* @package MetaModels/core
* @author Christian Schiffler
* @author Sven Baumann
- * @copyright 2012-2019 The MetaModels team.
+ * @author Ingolf Steinhardt
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
namespace MetaModels\CoreBundle\EventListener\DcGeneral\DefinitionBuilder;
-use Contao\Input;
-use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
-use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefaultModelRelationshipDefinition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\ModelRelationshipDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\FilterBuilder;
@@ -33,7 +31,9 @@
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
/**
- * This class is the abstract base for the condition builders.
+ * This class is the abstract base for the hierarchical/variant model condition builders.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
abstract class AbstractConditionBuilder
{
@@ -42,21 +42,21 @@ abstract class AbstractConditionBuilder
*
* @var IMetaModelDataDefinition
*/
- protected $container;
+ protected IMetaModelDataDefinition $container;
/**
* The input screen.
*
* @var array
*/
- protected $inputScreen;
+ protected array $inputScreen;
/**
* The model relationship interface.
*
* @var ModelRelationshipDefinitionInterface
*/
- protected $definition;
+ protected ModelRelationshipDefinitionInterface $definition;
/**
* Parse the correct conditions.
@@ -82,14 +82,25 @@ public static function calculateConditions(IMetaModelDataDefinition $container,
throw new \InvalidArgumentException('Search element does not implement the correct interface.');
}
- $instance = new static();
-
- $instance->container = $container;
- $instance->inputScreen = $inputScreen;
- $instance->definition = $definition;
+ $instance = new static($container, $inputScreen, $definition);
$instance->calculate();
}
+ /**
+ * @param IMetaModelDataDefinition $container
+ * @param array $inputScreen
+ * @param ModelRelationshipDefinitionInterface $definition
+ */
+ final public function __construct(
+ IMetaModelDataDefinition $container,
+ array $inputScreen,
+ ModelRelationshipDefinitionInterface $definition
+ ) {
+ $this->container = $container;
+ $this->inputScreen = $inputScreen;
+ $this->definition = $definition;
+ }
+
/**
* The real calculating function.
*
@@ -97,14 +108,14 @@ public static function calculateConditions(IMetaModelDataDefinition $container,
*
* @throws \RuntimeException When the conditions can not be determined.
*/
- abstract protected function calculate();
+ abstract protected function calculate(): void;
/**
* Parse the correct conditions for a MetaModel with variant support.
*
* @return void
*/
- protected function addParentCondition()
+ protected function addParentCondition(): void
{
if ($this->inputScreen['meta']['rendertype'] === 'standalone') {
return;
@@ -125,8 +136,8 @@ protected function addParentCondition()
->setDestinationName($this->container->getName());
$this->definition->addChildCondition($relationship);
} else {
- $setter = array_merge_recursive($setter, $relationship->getSetters());
- $inverse = array_merge_recursive($inverse, $relationship->getInverseFilterArray());
+ $setter = \array_merge_recursive($setter, $relationship->getSetters());
+ $inverse = \array_merge_recursive($inverse, $relationship->getInverseFilterArray());
}
// For tl_ prefix, the only unique target can be the id?
@@ -143,73 +154,11 @@ protected function addParentCondition()
}
/**
- * Parse the correct conditions for a MetaModel with variant support.
- *
- * @return void
- */
- protected function addHierarchicalConditions()
- {
- // Not hierarchical? Get out.
- if ($this->container->getBasicDefinition()->getMode() !== BasicDefinitionInterface::MODE_HIERARCHICAL) {
- return;
- }
-
- $relationship = $this->getRootCondition();
-
- // NOTE: this might bear problems when the definition will get serialized as the input value will not change.
- if (Input::get('pid')) {
- $parentValue = ModelId::fromSerialized(Input::get('pid'))->getId();
- } else {
- $parentValue = '0';
- }
-
- if (!$relationship->getSetters()) {
- $relationship
- ->setSetters([['property' => 'pid', 'value' => $parentValue]]);
- }
-
- $builder = FilterBuilder::fromArrayForRoot((array) $relationship->getFilterArray())->getFilter();
-
- $builder->andPropertyEquals('pid', $parentValue);
-
- $relationship
- ->setFilterArray($builder->getAllAsArray());
-
- $setter = [['to_field' => 'pid', 'from_field' => 'id']];
- $inverse = [];
-
- /** @var ParentChildConditionInterface $relationship */
- $relationship = $this->definition->getChildCondition($this->container->getName(), $this->container->getName());
- if ($relationship === null) {
- $relationship = new ParentChildCondition();
- $relationship
- ->setSourceName($this->container->getName())
- ->setDestinationName($this->container->getName());
- $this->definition->addChildCondition($relationship);
- } else {
- $setter = array_merge_recursive($setter, $relationship->getSetters());
- $inverse = array_merge_recursive($inverse, $relationship->getInverseFilterArray());
- }
-
- // For tl_ prefix, the only unique target can be the id?
- // maybe load parent dc and scan for unique in config then.
- $relationship
- ->setFilterArray(
- FilterBuilder::fromArray($relationship->getFilterArray())
- ->getFilter()
- ->andRemotePropertyEquals('pid', 'id')
- ->getAllAsArray()
- )
- ->setSetters($setter)
- ->setInverseFilterArray($inverse);
- }
-
- /**
- * Parse the correct conditions for a MetaModel with variant support.
+ * Parse the root conditions for a MetaModel with hierarchical/variant support.
*
* @return RootConditionInterface
*/
- protected function getRootCondition()
+ protected function getRootCondition(): RootConditionInterface
{
$rootProvider = $this->container->getName();
diff --git a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/BasicDefinitionBuilder.php b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/BasicDefinitionBuilder.php
index f210800e9..5390dcbfc 100644
--- a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/BasicDefinitionBuilder.php
+++ b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/BasicDefinitionBuilder.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2020 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2020 The MetaModels team.
+ * @copyright 2012-2023 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -25,6 +25,7 @@
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefaultBasicDefinition;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\IFactory;
+use MetaModels\IMetaModel;
use MetaModels\ViewCombination\ViewCombination;
/**
@@ -39,14 +40,14 @@ class BasicDefinitionBuilder
*
* @var ViewCombination
*/
- private $viewCombination;
+ private ViewCombination $viewCombination;
/**
* The factory.
*
* @var IFactory
*/
- private $factory;
+ private IFactory $factory;
/**
* Create a new instance.
@@ -70,7 +71,7 @@ public function __construct(ViewCombination $viewCombination, IFactory $factory)
protected function build(IMetaModelDataDefinition $container)
{
$inputScreen = $this->viewCombination->getScreen($container->getName());
- if (!$inputScreen) {
+ if (null === $inputScreen) {
return;
}
$meta = $inputScreen['meta'];
@@ -80,6 +81,7 @@ protected function build(IMetaModelDataDefinition $container)
$config->setDataProvider($container->getName());
$metaModel = $this->factory->getMetaModel($container->getName());
+ assert($metaModel instanceof IMetaModel);
// If we have variants, override all modes to tree mode.
if ($metaModel->hasVariants()) {
$config->setMode(BasicDefinitionInterface::MODE_HIERARCHICAL);
@@ -113,7 +115,7 @@ protected function build(IMetaModelDataDefinition $container)
*
* @return BasicDefinitionInterface
*/
- private function getOrCreateBasicDefinition(IMetaModelDataDefinition $container)
+ private function getOrCreateBasicDefinition(IMetaModelDataDefinition $container): BasicDefinitionInterface
{
if ($container->hasBasicDefinition()) {
return $container->getBasicDefinition();
diff --git a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/CommandBuilder.php b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/CommandBuilder.php
index 4e70036b2..8dbf8db1d 100644
--- a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/CommandBuilder.php
+++ b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/CommandBuilder.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2022 The MetaModels team.
+ * (c) 2012-2024 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,7 +14,7 @@
* @author Christian Schiffler
* @author Sven Baumann
* @author Ingolf Steinhardt
- * @copyright 2012-2022 The MetaModels team.
+ * @copyright 2012-2024 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
@@ -37,10 +37,12 @@
use MetaModels\IMetaModel;
use MetaModels\ViewCombination\ViewCombination;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
/**
* This class builds the commands.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CommandBuilder
{
@@ -51,49 +53,42 @@ class CommandBuilder
*
* @var EventDispatcherInterface
*/
- private $dispatcher;
+ private EventDispatcherInterface $dispatcher;
/**
* The view combinations.
*
* @var ViewCombination
*/
- private $viewCombination;
+ private ViewCombination $viewCombination;
/**
* The MetaModels factory.
*
* @var IFactory
*/
- private $factory;
+ private IFactory $factory;
/**
* The container (only set during build phase).
*
- * @var IMetaModelDataDefinition
- */
- private $container;
-
- /**
- * The input screen (only set during build phase).
- *
- * @var array
+ * @var IMetaModelDataDefinition|null
*/
- private $inputScreen;
+ private IMetaModelDataDefinition|null $container = null;
/**
* The icon builder.
*
* @var IconBuilder
*/
- private $iconBuilder;
+ private IconBuilder $iconBuilder;
/**
* The translator.
*
* @var TranslatorInterface
*/
- private $translator;
+ private TranslatorInterface $translator;
/**
* Create a new instance.
@@ -142,15 +137,14 @@ protected function build(IMetaModelDataDefinition $container)
);
}
- $this->container = $container;
- $this->inputScreen = $inputScreen = $this->viewCombination->getScreen($container->getName());
+ $this->container = $container;
+ $inputScreen = $this->viewCombination->getScreen($container->getName());
if (null === $inputScreen) {
return;
}
$this->addEditMultipleCommand($view);
$this->parseModelOperations($view);
- $this->container = null;
- $this->inputScreen = null;
+ $this->container = null;
if ($this->dispatcher->hasListeners(BuildMetaModelOperationsEvent::NAME)) {
// @codingStandardsIgnoreStart
@@ -159,11 +153,9 @@ protected function build(IMetaModelDataDefinition $container)
E_USER_DEPRECATED
);
// @codingStandardsIgnoreEnd
- $event = new BuildMetaModelOperationsEvent(
- $this->factory->getMetaModel($container->getName()),
- $container,
- $inputScreen
- );
+ $metaModel = $this->factory->getMetaModel($container->getName());
+ assert($metaModel instanceof IMetaModel);
+ $event = new BuildMetaModelOperationsEvent($metaModel, $container, $inputScreen);
$this->dispatcher->dispatch($event, $event::NAME);
}
}
@@ -175,8 +167,9 @@ protected function build(IMetaModelDataDefinition $container)
*
* @return void
*/
- private function addEditMultipleCommand(Contao2BackendViewDefinitionInterface $view)
+ private function addEditMultipleCommand(Contao2BackendViewDefinitionInterface $view): void
{
+ assert($this->container instanceof IMetaModelDataDefinition);
$definition = $this->container->getBasicDefinition();
// No actions allowed. Don't add the select command button.
if (!$definition->isEditable() && !$definition->isDeletable() && !$definition->isCreatable()) {
@@ -186,9 +179,9 @@ private function addEditMultipleCommand(Contao2BackendViewDefinitionInterface $v
$commands = $view->getGlobalCommands();
$command = new SelectCommand();
$command
- ->setName('all')
- ->setLabel('MSC.all.0')
- ->setDescription('MSC.all.1');
+ ->setName('editAll')
+ ->setLabel('editAll.label')
+ ->setDescription('editAll.description');
$parameters = $command->getParameters();
$parameters['act'] = 'select';
@@ -205,8 +198,9 @@ private function addEditMultipleCommand(Contao2BackendViewDefinitionInterface $v
*
* @return void
*/
- private function parseModelOperations(Contao2BackendViewDefinitionInterface $view)
+ private function parseModelOperations(Contao2BackendViewDefinitionInterface $view): void
{
+ assert($this->container instanceof IMetaModelDataDefinition);
$collection = $view->getModelCommands();
$scrOffsetAttributes = ['attributes' => 'onclick="Backend.getScrollOffset();"'];
@@ -219,15 +213,15 @@ private function parseModelOperations(Contao2BackendViewDefinitionInterface $vie
['act' => 'delete'],
'delete.svg',
[
- 'attributes' => sprintf(
- 'onclick="if (!confirm(\'%s\')) return false; Backend.getScrollOffset();"',
- $this->translator->trans('MSC.deleteConfirm', [], 'contao_default')
- )
+ 'attributes' =>
+ 'onclick="if (!confirm(this.dataset.msgConfirm)) return false; Backend.getScrollOffset();"',
]
);
$this->createCommand($collection, 'show', ['act' => 'show'], 'show.svg');
- if ($this->factory->getMetaModel($this->container->getName())->hasVariants()) {
+ $metaModel = $this->factory->getMetaModel($this->container->getName());
+ assert($metaModel instanceof IMetaModel);
+ if ($metaModel->hasVariants()) {
$this->createCommand(
$collection,
'createvariant',
@@ -238,17 +232,14 @@ private function parseModelOperations(Contao2BackendViewDefinitionInterface $vie
// Check if we have some children.
foreach ($this->viewCombination->getChildrenOf($this->container->getName()) as $tableName => $screen) {
- $metaModel = $this->factory->getMetaModel($tableName);
- $caption = $this->getChildModelCaption($metaModel, $screen);
-
$this->createCommand(
$collection,
'edit_' . $tableName,
['table' => $tableName],
$this->iconBuilder->getBackendIcon($screen['meta']['backendicon']),
[
- 'label' => $caption[0],
- 'description' => $caption[1],
+ 'label' => 'metamodel_edit_as_child.' . $tableName . '.label',
+ 'description' => 'metamodel_edit_as_child.' . $tableName . '.description',
'idparam' => 'pid'
]
);
@@ -256,7 +247,7 @@ private function parseModelOperations(Contao2BackendViewDefinitionInterface $vie
}
/**
- * Build a command into the the command collection.
+ * Build a command into the command collection.
*
* @param CommandCollectionInterface $collection The command collection.
* @param string $operationName The operation name.
@@ -268,10 +259,10 @@ private function parseModelOperations(Contao2BackendViewDefinitionInterface $vie
*/
private function createCommand(
CommandCollectionInterface $collection,
- $operationName,
- $queryParameters,
- $icon,
- $extraValues = []
+ string $operationName,
+ array $queryParameters,
+ string $icon,
+ array $extraValues = []
) {
$command = $this->getCommandInstance($collection, $operationName);
$parameters = $command->getParameters();
@@ -282,14 +273,14 @@ private function createCommand(
}
if (!$command->getLabel()) {
- $command->setLabel($operationName . '.0');
+ $command->setLabel($operationName . '.label');
if (isset($extraValues['label'])) {
$command->setLabel($extraValues['label']);
}
}
if (!$command->getDescription()) {
- $command->setDescription($operationName . '.1');
+ $command->setDescription($operationName . '.description');
if (isset($extraValues['description'])) {
$command->setDescription($extraValues['description']);
}
@@ -309,12 +300,11 @@ private function createCommand(
* Retrieve or create a command instance of the given name.
*
* @param CommandCollectionInterface $collection The command collection.
- *
* @param string $operationName The name of the operation.
*
* @return CommandInterface
*/
- private function getCommandInstance(CommandCollectionInterface $collection, $operationName)
+ private function getCommandInstance(CommandCollectionInterface $collection, string $operationName)
{
if ($collection->hasCommandNamed($operationName)) {
$command = $collection->getCommandNamed($operationName);
@@ -338,37 +328,4 @@ private function getCommandInstance(CommandCollectionInterface $collection, $ope
return $command;
}
-
- /**
- * Create the caption text for the child model.
- *
- * @param IMetaModel $metaModel The child model.
- * @param array $screen The input screen.
- *
- * @return array
- *
- * @SuppressWarnings(PHPMD.Superglobals)
- * @SuppressWarnings(PHPMD.CamelCaseVariableName)
- */
- private function getChildModelCaption($metaModel, $screen)
- {
- $caption = [
- '',
- sprintf(
- $GLOBALS['TL_LANG']['MSC']['metamodel_edit_as_child']['label'],
- $metaModel->getName()
- )
- ];
-
- foreach ($screen['label'] as $langCode => $label) {
- if (!empty($label) && $langCode === \str_replace('-', '_', $GLOBALS['TL_LANGUAGE'])) {
- $caption = [
- $screen['description'][$langCode],
- $label
- ];
- }
- }
-
- return $caption;
- }
}
diff --git a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/ConditionBuilderWithVariants.php b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/ConditionBuilderWithVariants.php
index 43328f95f..df30a3f5e 100644
--- a/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/ConditionBuilderWithVariants.php
+++ b/src/CoreBundle/EventListener/DcGeneral/DefinitionBuilder/ConditionBuilderWithVariants.php
@@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
- * (c) 2012-2019 The MetaModels team.
+ * (c) 2012-2023 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,20 +12,24 @@
*
* @package MetaModels/core
* @author Christian Schiffler
+ * @author Stefan Heimes