diff --git a/src/Contao/Callback/Callbacks.php b/src/Contao/Callback/Callbacks.php index ae2c8ef3..115aef6e 100644 --- a/src/Contao/Callback/Callbacks.php +++ b/src/Contao/Callback/Callbacks.php @@ -132,6 +132,9 @@ protected static function evaluateCallback($callback) if ($serviceCallback[0] !== $callback[0]) { return $serviceCallback; } + if (!\class_exists($callback[0])) { + return $callback; + } $class = new ReflectionClass($callback[0]); diff --git a/src/Contao/Compatibility/DcCompat.php b/src/Contao/Compatibility/DcCompat.php index 6d1fefa8..f4a9e883 100644 --- a/src/Contao/Compatibility/DcCompat.php +++ b/src/Contao/Compatibility/DcCompat.php @@ -22,30 +22,35 @@ namespace ContaoCommunityAlliance\DcGeneral\Contao\Compatibility; +use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface; use ContaoCommunityAlliance\DcGeneral\Data\ModelId; use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\DC\General; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralException; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException; use ContaoCommunityAlliance\DcGeneral\Factory\Event\PopulateEnvironmentEvent; +use ContaoCommunityAlliance\DcGeneral\InputProviderInterface; /** * Small compatibility layer for callbacks, that expect a "full-featured" DC instance. + * + * @psalm-suppress PropertyNotSetInConstructor */ class DcCompat extends General { /** * The current model. * - * @var ModelInterface + * @var ModelInterface|null */ protected $model; /** * Name of the property currently working on. * - * @var string + * @var string|null */ protected $propertyName; @@ -53,7 +58,7 @@ class DcCompat extends General * Create a new instance. * * @param EnvironmentInterface $environment The Dc instance to use for delegating. - * @param ModelInterface $model The model within scope (optional). + * @param ModelInterface|null $model The model within scope (optional). * @param string|null $propertyName The name of the property within scope (optional). */ public function __construct(EnvironmentInterface $environment, ModelInterface $model = null, $propertyName = null) @@ -71,7 +76,7 @@ public function __construct(EnvironmentInterface $environment, ModelInterface $m /** * Retrieve the current model. * - * @return ModelInterface + * @return ModelInterface|null */ public function getModel() { @@ -81,7 +86,7 @@ public function getModel() /** * Retrieve the current property. * - * @return string + * @return string|null */ public function getPropertyName() { @@ -89,9 +94,13 @@ public function getPropertyName() } /** - * {@inheritdoc} + * Internal use only. * * @throws DcGeneralException This method is for internal use only. + * + * @return never + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function handlePopulateEnvironment(PopulateEnvironmentEvent $event) { @@ -105,7 +114,7 @@ public function handlePopulateEnvironment(PopulateEnvironmentEvent $event) * * @throws DcGeneralException This method is for internal use only. */ - protected function getTablenameCallback($strTable) + protected function getTablenameCallback($tableName) { throw new DcGeneralException( __CLASS__ . '::getTablenameCallback() is internal use only and must not be called' @@ -134,12 +143,18 @@ public function __get($name) switch ($name) { case 'id': if (null !== $this->getModel()) { - return $this->getModel()->getId(); + $model = $this->getModel(); + assert($model instanceof ModelInterface); + return $model->getId(); } - $environment = $this->getEnvironment(); + $environment = $this->getEnvironment(); + $dataDefinition = $environment->getDataDefinition(); - $inputProvider = $environment->getInputProvider(); + assert($dataDefinition instanceof ContainerInterface); + + $inputProvider = $environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); $idParameter = $inputProvider->hasParameter('id') ? 'id' : 'pid'; if (!$inputProvider->hasParameter($idParameter)) { @@ -168,7 +183,10 @@ public function __get($name) case 'parentTable': if ($this->getEnvironment()->getParentDataDefinition()) { - return $this->getEnvironment()->getParentDataDefinition()->getName(); + $container = $this->getEnvironment()->getParentDataDefinition(); + assert($container instanceof ContainerInterface); + + return $container->getName(); } return null; @@ -182,11 +200,16 @@ public function __get($name) throw new DcGeneralRuntimeException('The magic property $dc->createNewVersion is not supported yet!'); case 'table': - return $this->getEnvironment()->getDataProvider()->getEmptyModel()->getProviderName(); + $dataProvider = $this->getEnvironment()->getDataProvider(); + assert($dataProvider instanceof DataProviderInterface); + + return $dataProvider->getEmptyModel()->getProviderName(); case 'value': if ($this->propertyName && $this->getModel()) { - return $this->getModel()->getProperty($this->propertyName); + $model = $this->getModel(); + assert($model instanceof ModelInterface); + return $model->getProperty($this->propertyName); } return null; @@ -200,6 +223,7 @@ public function __get($name) throw new DcGeneralRuntimeException('The magic property $dc->palette is not supported yet!'); case 'activeRecord': + assert($this->model instanceof ModelInterface); return new ActiveRecord($this->model); default: diff --git a/src/Contao/Dca/Builder/Legacy/LegacyDcaDataDefinitionBuilder.php b/src/Contao/Dca/Builder/Legacy/LegacyDcaDataDefinitionBuilder.php index 9c35455e..ba96bb0a 100644 --- a/src/Contao/Dca/Builder/Legacy/LegacyDcaDataDefinitionBuilder.php +++ b/src/Contao/Dca/Builder/Legacy/LegacyDcaDataDefinitionBuilder.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general. * - * (c) 2013-2022 Contao Community Alliance. + * (c) 2013-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,7 +17,7 @@ * @author Stefan Heimes * @author Sven Baumann * @author Ingolf Steinhardt - * @copyright 2013-2022 Contao Community Alliance. + * @copyright 2013-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later * @filesource */ @@ -162,7 +162,7 @@ public function build(ContainerInterface $container, BuildDataDefinitionEvent $e * Register the callback handlers for the given legacy callbacks. * * @param EventDispatcherInterface $dispatcher The event dispatcher. - * @param array $callbacks The callbacks to be handled. + * @param list|callable $callbacks The callbacks to be handled. * @param string $eventName The event to be registered to. * @param array $arguments The arguments to pass to the constructor. * @param class-string $listener The listener class to use. @@ -175,14 +175,11 @@ public function build(ContainerInterface $container, BuildDataDefinitionEvent $e */ protected function parseCallback($dispatcher, $callbacks, $eventName, $arguments, $listener) { - if (!(is_array($callbacks) || is_callable($callbacks))) { - return; - } - // If only one callback given, ensure the loop below handles it correctly. - if (is_array($callbacks) && (2 === count($callbacks)) && !is_array($callbacks[0])) { + if (is_array($callbacks) && (2 === count($callbacks)) && !is_array($callbacks[0] ?? [])) { $callbacks = [$callbacks]; } + foreach ((array) $callbacks as $callback) { if ($this->isCallbackBlacklisted($callback, $listener)) { continue; @@ -198,17 +195,17 @@ protected function parseCallback($dispatcher, $callbacks, $eventName, $arguments /** * Check if callback is blacklisted. * - * @param mixed $callback The callback. - * @param string $listener The listener class. + * @param mixed $callback The callback. + * @param class-string $listener The listener class. * * @return bool */ - private function isCallbackBlacklisted($callback, $listener) + private function isCallbackBlacklisted(mixed $callback, string $listener): bool { return ((ContainerOnLoadCallbackListener::class === $listener) && is_array($callback) && ('checkPermission' === $callback[1]) - && (0 === strpos($callback[0], 'tl_'))); + && (str_starts_with($callback[0], 'tl_'))); } /** diff --git a/src/Contao/View/Contao2BackendView/ActionHandler/MultipleHandler/OverrideAllHandler.php b/src/Contao/View/Contao2BackendView/ActionHandler/MultipleHandler/OverrideAllHandler.php index b70cced3..ac676e98 100644 --- a/src/Contao/View/Contao2BackendView/ActionHandler/MultipleHandler/OverrideAllHandler.php +++ b/src/Contao/View/Contao2BackendView/ActionHandler/MultipleHandler/OverrideAllHandler.php @@ -350,7 +350,11 @@ private function renderFieldSets( private function getModelFromWidget(Widget $widget): ModelInterface { if ($widget->dataContainer instanceof DcCompat) { - return $widget->dataContainer->getModel(); + $model = $widget->dataContainer->getModel(); + if (null === $model) { + throw new \InvalidArgumentException('Datacontainer does not hold a model.'); + } + return $model; } if ($widget instanceof AbstractWidget) { return $widget->getModel(); diff --git a/src/Contao/View/Contao2BackendView/EditMask.php b/src/Contao/View/Contao2BackendView/EditMask.php index 64cadd2e..f409436c 100644 --- a/src/Contao/View/Contao2BackendView/EditMask.php +++ b/src/Contao/View/Contao2BackendView/EditMask.php @@ -1021,17 +1021,10 @@ public function execute() private function executeMultiLanguage(ContaoBackendViewTemplate $template) { $dataProvider = $this->getEnvironment()->getDataProvider($this->model->getProviderName()); - assert($dataProvider instanceof DataProviderInterface); - if ( - \in_array( - MultiLanguageDataProviderInterface::class, - \class_implements($dataProvider) - ) + $dataProvider instanceof MultiLanguageDataProviderInterface + && null !== $dataProvider->getLanguages($this->model->getId()) ) { - /** @var MultiLanguageDataProviderInterface $dataProvider */ - $dataProvider = $this->getEnvironment()->getDataProvider(); - $locales = System::getContainer()->get('contao.intl.locales'); assert($locales instanceof Locales); @@ -1043,10 +1036,8 @@ private function executeMultiLanguage(ContaoBackendViewTemplate $template) $translator = $this->environment->getTranslator(); assert($translator instanceof TranslatorInterface); - $template->set( - 'languages', - $controller->getSupportedLanguages($this->model->getId()) - ) + $template + ->set('languages', $controller->getSupportedLanguages($this->model->getId())) ->set('language', $dataProvider->getCurrentLanguage()) ->set('languageSubmit', $translator->translate('MSC.showSelected')) ->set('languageHeadline', $languages[$dataProvider->getCurrentLanguage()] ?? ''); diff --git a/src/Contao/View/Contao2BackendView/Subscriber/GetGroupHeaderSubscriber.php b/src/Contao/View/Contao2BackendView/Subscriber/GetGroupHeaderSubscriber.php index b3282c20..af9edb7b 100644 --- a/src/Contao/View/Contao2BackendView/Subscriber/GetGroupHeaderSubscriber.php +++ b/src/Contao/View/Contao2BackendView/Subscriber/GetGroupHeaderSubscriber.php @@ -53,14 +53,14 @@ class GetGroupHeaderSubscriber * * @var EventDispatcherInterface */ - private $dispatcher; + private EventDispatcherInterface $dispatcher; /** * The translator. * * @var TranslatorInterface */ - private $translator; + private TranslatorInterface $translator; /** * Create a new instance. @@ -136,6 +136,7 @@ protected function formatGroupHeader( if (isset($evaluation['multiple']) && !$evaluation['multiple'] && ('checkbox' === $property->getWidgetType())) { return $this->formatCheckboxOptionLabel($model->getProperty($property->getName())); } + if (GroupAndSortingInformationInterface::GROUP_NONE !== $groupingMode) { return $this->formatByGroupingMode($groupingMode, $groupingLength, $environment, $property, $model); } @@ -240,9 +241,9 @@ private function formatByCharGrouping($value, $groupingLength) * * @param int $value The value. * - * @return string|null + * @return string */ - private function formatByDayGrouping(int $value): ?string + private function formatByDayGrouping(int $value): string { $value = $this->getTimestamp($value); @@ -263,13 +264,14 @@ private function formatByDayGrouping(int $value): ?string * * @return string */ - private function formatByWeekGrouping(int $value): ?string + private function formatByWeekGrouping(int $value): string { $value = $this->getTimestamp($value); if (0 === $value) { return '-'; } + $event = new ParseDateEvent($value, $this->translator->translate('MSC.week_format')); $this->dispatcher->dispatch($event, ContaoEvents::DATE_PARSE); @@ -281,15 +283,16 @@ private function formatByWeekGrouping(int $value): ?string * * @param int $value The value. * - * @return string|null + * @return string */ - private function formatByMonthGrouping(int $value): ?string + private function formatByMonthGrouping(int $value): string { $value = $this->getTimestamp($value); if (0 === $value) { return '-'; } + $event = new ParseDateEvent($value, 'F Y'); $this->dispatcher->dispatch($event, ContaoEvents::DATE_PARSE); @@ -317,11 +320,11 @@ private function formatByYearGrouping($value) /** * Make sure a timestamp is returned. * - * @param int|\DateTime $value The given date. + * @param \DateTime|int $value The given date. * * @return int */ - private function getTimestamp($value) + private function getTimestamp(\DateTime|int $value): int { return ($value instanceof \DateTime) ? $value->getTimestamp() : $value; } diff --git a/src/Contao/View/Contao2BackendView/Widget/AbstractWidget.php b/src/Contao/View/Contao2BackendView/Widget/AbstractWidget.php index f47c3e0e..fb462e22 100644 --- a/src/Contao/View/Contao2BackendView/Widget/AbstractWidget.php +++ b/src/Contao/View/Contao2BackendView/Widget/AbstractWidget.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general. * - * (c) 2013-2019 Contao Community Alliance. + * (c) 2013-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,7 +14,8 @@ * @author David Molineus * @author Christian Schiffler * @author Sven Baumann - * @copyright 2013-2019 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2013-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general/blob/master/LICENSE LGPL-3.0-or-later * @filesource */ @@ -91,6 +92,9 @@ public function getEnvironment() public function getModel() { assert($this->dataContainer instanceof DcCompat); - return $this->dataContainer->getModel(); + $model = $this->dataContainer->getModel(); + assert($model instanceof ModelInterface); + + return $model; } } diff --git a/src/DC/General.php b/src/DC/General.php index d2f2f504..6d3c8672 100644 --- a/src/DC/General.php +++ b/src/DC/General.php @@ -50,6 +50,8 @@ /** * This class is only present so Contao can instantiate a backend properly as it needs a \DataContainer descendant. * + * @psalm-suppress PropertyNotSetInConstructor + * * @SuppressWarnings(PHPMD.TooManyPublicMethods) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -58,7 +60,7 @@ class General extends DataContainer implements DataContainerInterface /** * The environment attached to this DC. * - * @var EnvironmentInterface + * @var EnvironmentInterface|null */ protected $objEnvironment; @@ -95,6 +97,7 @@ public function __construct($tableName, array $module = [], CacheInterface $cach } $this->objEnvironment = $event->getEnvironment(); }, $this, $this); + assert($fetcher instanceof \Closure); $dispatcher->addListener(PopulateEnvironmentEvent::NAME, $fetcher, 4800); (new DcGeneralFactory($cache)) @@ -121,9 +124,12 @@ public function __construct($tableName, array $module = [], CacheInterface $cach * * @return EventDispatcherInterface */ - private function getEventDispatcher() + private function getEventDispatcher(): EventDispatcherInterface { - return System::getContainer()->get('event_dispatcher'); + $dispatcher = System::getContainer()->get('event_dispatcher'); + assert($dispatcher instanceof EventDispatcherInterface); + + return $dispatcher; } /** @@ -131,9 +137,12 @@ private function getEventDispatcher() * * @return TranslatorInterface */ - private function getTranslator() + private function getTranslator(): TranslatorInterface { - return System::getContainer()->get('cca.translator.contao_translator'); + $translator = System::getContainer()->get('cca.translator.contao_translator'); + assert($translator instanceof TranslatorInterface); + + return $translator; } /** @@ -240,7 +249,7 @@ public function getName() */ public function getEnvironment() { - if (!$this->objEnvironment) { + if (null === $this->objEnvironment) { throw new DcGeneralRuntimeException('No Environment set.'); } @@ -254,7 +263,10 @@ public function getEnvironment() */ public function getViewHandler() { - return $this->getEnvironment()->getView(); + $view = $this->getEnvironment()->getView(); + assert($view instanceof ViewInterface); + + return $view; } /** @@ -264,7 +276,10 @@ public function getViewHandler() */ public function getControllerHandler() { - return $this->getEnvironment()->getController(); + $controller = $this->getEnvironment()->getController(); + assert($controller instanceof ControllerInterface); + + return $controller; } /** @@ -416,7 +431,7 @@ public function undo() * * @deprecated Only here as requirement of \DataContainer * - * @return void + * @return never * * @throws DcGeneralRuntimeException Throws exception because method is not supported. */ diff --git a/src/DataContainerInterface.php b/src/DataContainerInterface.php index ea9fec8d..2b7c84c0 100644 --- a/src/DataContainerInterface.php +++ b/src/DataContainerInterface.php @@ -21,9 +21,15 @@ namespace ContaoCommunityAlliance\DcGeneral; +use Contao\EditableDataContainerInterface; +use Contao\ListableDataContainerInterface; + /** * This interface describes an object providing access to an environment. */ -interface DataContainerInterface extends \editable, \listable, EnvironmentAwareInterface +interface DataContainerInterface extends + EditableDataContainerInterface, + ListableDataContainerInterface, + EnvironmentAwareInterface { } diff --git a/src/Panel/DefaultLimitElement.php b/src/Panel/DefaultLimitElement.php index 77e50b44..c518df7f 100644 --- a/src/Panel/DefaultLimitElement.php +++ b/src/Panel/DefaultLimitElement.php @@ -199,7 +199,7 @@ private function defineOffsetAndAmountOption(int &$offset, int &$amount): void return; } - if ($input->hasValue('tl_limit') && $this->getPanel()?->getContainer()->updateValues()) { + if ($input->hasValue('tl_limit') && $this->getPanel()->getContainer()->updateValues()) { $limit = $input->getValue('tl_limit'); if ('all' === $limit) { $offset = 0;