<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral;

use ContaoCommunityAlliance\DcGeneral\Factory\Event\PreCreateDcGeneralEvent;
use MetaModels\DcGeneral\DataDefinition\MetaModelDataDefinition;
use MetaModels\IFactory;

/**
 * This class changes the definition container type.
 */
class PreCreateListener
{
    /**
     * The MetaModels factory.
     *
     * @var IFactory
     */
    private $factory;

    /**
     * Create a new instance.
     *
     * @param IFactory $factory The MetaModels factory.
     */
    public function __construct(IFactory $factory)
    {
        $this->factory = $factory;
    }

    /**
     * Handle the event.
     *
     * @param PreCreateDcGeneralEvent $event The event.
     *
     * @return void
     */
    public function handle(PreCreateDcGeneralEvent $event)
    {
        $factory = $event->getFactory();
        if (!in_array($factory->getContainerName(), $this->factory->collectNames())) {
            return;
        }

        $factory->setContainerClassName(MetaModelDataDefinition::class);
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     David Molineus <david.molineus@netzmacht.de>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\DcaSortGroup;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\ConditionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionChain;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\PropertyInterface;
use ContaoCommunityAlliance\DcGeneral\Factory\Event\BuildDataDefinitionEvent;
use Doctrine\DBAL\Connection;
use MetaModels\DcGeneral\DataDefinition\Palette\Condition\Property\InputScreenRenderModeIs;

/**
 * This hides the rendergrouptype for tree views.
 */
class VisibilityConditionBuildingListener
{
    /**
     * Database connection.
     *
     * @var Connection
     */
    private $connection;

    /**
     * PaletteRestrictionListener constructor.
     *
     * @param Connection $connection Database connection.
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * Set the visibility condition for the widget.
     *
     * Manipulate the data definition for the property "rendergrouptype" in table "tl_metamodel_dca_sortgroup".
     *
     * @param BuildDataDefinitionEvent $event The event.
     *
     * @return void
     */
    public function handle(BuildDataDefinitionEvent $event)
    {
        if ('tl_metamodel_dca_sortgroup' !== $event->getContainer()->getName()) {
            return;
        }

        foreach ($event->getContainer()->getPalettesDefinition()->getPalettes() as $palette) {
            foreach ($palette->getProperties() as $property) {
                if ($property->getName() != 'rendergrouptype') {
                    continue;
                }

                $this->addCondition(
                    $property,
                    new PropertyConditionChain(
                        array(
                            new InputScreenRenderModeIs('flat', $this->connection),
                            new InputScreenRenderModeIs('parented', $this->connection),
                        ),
                        PropertyConditionChain::OR_CONJUNCTION
                    )
                );
            }
        }
    }

    /**
     * Add a visible condition.
     *
     * @param PropertyInterface  $property  The property.
     *
     * @param ConditionInterface $condition The condition to add.
     *
     * @return void
     */
    private function addCondition(PropertyInterface $property, ConditionInterface $condition)
    {
        $chain = $property->getVisibleCondition();
        if (!($chain
            && ($chain instanceof PropertyConditionChain)
            && $chain->getConjunction() == PropertyConditionChain::AND_CONJUNCTION
        )
        ) {
            if ($property->getVisibleCondition()) {
                $previous = array($property->getVisibleCondition());
            } else {
                $previous = array();
            }

            $chain = new PropertyConditionChain(
                $previous,
                PropertyConditionChain::AND_CONJUNCTION
            );

            $property->setVisibleCondition($chain);
        }

        $chain->addCondition($condition);
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\FilterSetting;

use Contao\StringUtil;
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\Backend\AddToUrlEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ModelToLabelEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\Translator\TranslatorInterface;
use MetaModels\CoreBundle\Assets\IconBuilder;
use MetaModels\Filter\Setting\IFilterSettingFactory;
use MetaModels\IMetaModel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Handles rendering of model from tl_metamodel_filtersetting.
 */
abstract class AbstractFilterSettingTypeRenderer
{
    /**
     * The filter setting factory.
     *
     * @var IFilterSettingFactory
     */
    private $factory;

    /**
     * The event dispatcher.
     *
     * @var EventDispatcherInterface
     */
    private $dispatcher;

    /**
     * The icon builder.
     *
     * @var IconBuilder
     */
    private $iconBuilder;

    /**
     * Request scope determinator.
     *
     * @var RequestScopeDeterminator
     */
    private $scopeMatcher;

    /**
     * Create a new instance.
     *
     * @param IFilterSettingFactory    $filterSettingFactory The filter factory.
     * @param EventDispatcherInterface $dispatcher           The event dispatcher.
     * @param IconBuilder              $iconBuilder          The icon builder.
     * @param RequestScopeDeterminator $scopeMatcher         Request scope determinator.
     */
    public function __construct(
        IFilterSettingFactory $filterSettingFactory,
        EventDispatcherInterface $dispatcher,
        IconBuilder $iconBuilder,
        RequestScopeDeterminator $scopeMatcher
    ) {
        $this->factory     = $filterSettingFactory;
        $this->dispatcher  = $dispatcher;
        $this->iconBuilder = $iconBuilder;

        $this->scopeMatcher = $scopeMatcher;
    }

    /**
     * Render a filter setting into html.
     *
     * @param ModelToLabelEvent $event The event.
     *
     * @return void
     */
    public function modelToLabel(ModelToLabelEvent $event)
    {
        if (!$this->scopeMatcher->currentScopeIsBackend()) {
            return;
        }

        $model = $event->getModel();
        if (($model->getProviderName() !== 'tl_metamodel_filtersetting')
            || !in_array($event->getModel()->getProperty('type'), $this->getTypes())
        ) {
            return;
        }

        $environment = $event->getEnvironment();

        $event
            ->setLabel($this->getLabelPattern($environment, $model))
            ->setArgs($this->getLabelParameters($environment, $model));
    }

    /**
     * Retrieve the MetaModel attached to the model filter setting.
     *
     * @param ModelInterface $model The model for which to retrieve the MetaModel.
     *
     * @return IMetaModel
     */
    protected function getMetaModel(ModelInterface $model)
    {
        // NOTE: It is maybe not that wise to instantiate the whole filter setting here?
        $filterSetting = $this->factory->createCollection($model->getProperty('fid'));

        return $filterSetting->getMetaModel();
    }

    /**
     * Retrieve the types this renderer is valid for.
     *
     * @return string[]
     */
    abstract protected function getTypes();

    /**
     * Retrieve the comment for the label.
     *
     * @param ModelInterface      $model      The filter setting to render.
     * @param TranslatorInterface $translator The translator in use.
     *
     * @return string
     */
    protected function getLabelComment(ModelInterface $model, TranslatorInterface $translator)
    {
        if ($model->getProperty('comment')) {
            return sprintf(
                $translator->translate('typedesc._comment_', 'tl_metamodel_filtersetting'),
                StringUtil::specialchars($model->getProperty('comment'))
            );
        }
        return '';
    }

    /**
     * Retrieve the image for the label.
     *
     * @param ModelInterface $model The filter setting to render.
     *
     * @return string
     */
    protected function getLabelImage(ModelInterface $model)
    {
        $typeFactory = $this->factory->getTypeFactory($model->getProperty('type'));

        $image = $this->iconBuilder->getBackendIconImageTag(
            $this->updateImageWithDisabled($model, $typeFactory->getTypeIcon()),
            '',
            '',
            $this->updateImageWithDisabled($model, 'bundles/metamodelscore/images/icons/filter_default.png')
        );

        /** @var AddToUrlEvent $urlEvent */
        $urlEvent = $this->dispatcher->dispatch(
            ContaoEvents::BACKEND_ADD_TO_URL,
            new AddToUrlEvent('act=edit&id='.$model->getId())
        );

        return sprintf(
            '%s',
            $urlEvent->getUrl(),
            $image
        );
    }

    /**
     * Retrieve the label text for a filter setting.
     *
     * @param TranslatorInterface $translator The translator in use.
     * @param ModelInterface      $model      The filter setting to render.
     *
     * @return mixed|string
     */
    protected function getLabelText(TranslatorInterface $translator, ModelInterface $model)
    {
        $type  = $model->getProperty('type');
        $label = $translator->translate('typenames.' . $type, 'tl_metamodel_filtersetting');
        if ($label == 'typenames.' . $type) {
            return $type;
        }
        return $label;
    }

    /**
     * Retrieve the label pattern.
     *
     * @param EnvironmentInterface $environment The translator in use.
     * @param ModelInterface       $model       The filter setting to render.
     *
     * @return string
     */
    protected function getLabelPattern(EnvironmentInterface $environment, ModelInterface $model)
    {
        $translator = $environment->getTranslator();
        $type       = $model->getProperty('type');
        $combined   = 'typedesc.' . $type;

        if (($resultPattern = $translator->translate($combined, 'tl_metamodel_filtersetting')) == $combined) {
            $resultPattern = $translator->translate('typedesc._default_', 'tl_metamodel_filtersetting');
        }

        return $resultPattern;
    }

    /**
     * Retrieve the parameters for the label with attribute name and url parameter.
     *
     * @param EnvironmentInterface $environment The translator in use.
     * @param ModelInterface       $model       The model.
     *
     * @return array
     */
    protected function getLabelParametersWithAttributeAndUrlParam(
        EnvironmentInterface $environment,
        ModelInterface $model
    ) {
        $translator = $environment->getTranslator();
        $metamodel  = $this->getMetaModel($model);
        $attribute  = $metamodel->getAttributeById($model->getProperty('attr_id'));

        if ($attribute) {
            $attributeName = $attribute->getColName();
        } else {
            $attributeName = $model->getProperty('attr_id');
        }

        return array(
                $this->getLabelImage($model),
                $this->getLabelText($translator, $model),
                $this->getLabelComment($model, $translator),
                $attributeName,
                ($model->getProperty('urlparam') ? $model->getProperty('urlparam') : $attributeName)
            );
    }

    /**
     * Retrieve the parameters for the label with attribute name and url parameter.
     *
     * @param EnvironmentInterface $environment The translator in use.
     * @param ModelInterface       $model       The model.
     *
     * @return array
     */
    protected function getLabelParametersNormal(EnvironmentInterface $environment, ModelInterface $model)
    {
        $translator = $environment->getTranslator();

        return array(
            $this->getLabelImage($model),
            $this->getLabelText($translator, $model),
            $this->getLabelComment($model, $translator),
            $model->getProperty('type')
        );
    }

    /**
     * Retrieve the parameters for the label.
     *
     * @param EnvironmentInterface $environment The translator in use.
     * @param ModelInterface       $model       The model.
     *
     * @return array
     */
    protected function getLabelParameters(EnvironmentInterface $environment, ModelInterface $model)
    {
        return $this->getLabelParametersNormal($environment, $model);
    }

    /**
     * Add the '_1' suffix to the image if it is disabled.
     *
     * @param ModelInterface $model The model.
     * @param string         $image The image to alter.
     *
     * @return mixed
     */
    private function updateImageWithDisabled(ModelInterface $model, $image)
    {
        $this->preCreateInverseImage($model, $image);

        if ($model->getProperty('enabled')) {
            return $image;
        }
        if (false === $intPos = strrpos($image, '.')) {
            return $image;
        }

        return substr_replace($image, '_1', $intPos, 0);
    }

    /**
     * Pre create the inverse image.
     *
     * @param ModelInterface $model The model.
     * @param string         $image The image for pre create.
     *
     * @return void
     */
    private function preCreateInverseImage(ModelInterface $model, string $image): void
    {
        if (false === $intPos = strrpos($image, '.')) {
            return;
        }

        if ($model->getProperty('enabled')) {
            $this->iconBuilder->getBackendIcon(substr_replace($image, '_1', $intPos, 0));

            return;
        }

        $this->iconBuilder->getBackendIcon($image);
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 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 MetaModels\CoreBundle\Assets\IconBuilder;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * The breadcrumb store.
 */
class BreadcrumbStore
{
    /**
     * The elements.
     *
     * @var array
     */
    private $elements = [];

    /**
     * The icon builder.
     *
     * @var IconBuilder
     */
    private $iconBuilder;

    /**
     * The translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * List of "current" ids.
     *
     * @var string[]
     */
    private $idList = [];

    /**
     * The current URI.
     *
     * @var string
     */
    private $uri;

    /**
     * Create a new instance.
     *
     * @param IconBuilder         $iconBuilder The icon builder.
     * @param TranslatorInterface $translator  The translator.
     * @param string              $uri         The current URI.
     */
    public function __construct(IconBuilder $iconBuilder, TranslatorInterface $translator, $uri)
    {
        $this->iconBuilder = $iconBuilder;
        $this->translator  = $translator;
        $this->uri         = $uri;
    }

    /**
     * Push an entry.
     *
     * @param string $url   The url.
     * @param string $table The table.
     * @param string $icon  The icon.
     *
     * @return void
     */
    public function push($url, $table, $icon)
    {
        $this->elements[] = [
            'url' => $url,
            'text' => $this->getLabel($table),
            'icon' => $this->iconBuilder->getBackendIcon($icon)
        ];
    }

    /**
     * Test if an id has been set.
     *
     * @param string $table The table name.
     *
     * @return bool
     */
    public function hasId($table): bool
    {
        return array_key_exists($table, $this->idList);
    }

    /**
     * Set an id.
     *
     * @param string $table  The table name.
     * @param string $itemId The id.
     *
     * @return void
     */
    public function setId($table, $itemId)
    {
        $this->idList[$table] = $itemId;
    }

    /**
     * Get an id.
     *
     * @param string $table The table name.
     *
     * @return null|string
     */
    public function getId($table)
    {
        return $this->hasId($table) ? $this->idList[$table] : null;
    }

    /**
     * Retrieve uri.
     *
     * @return string
     */
    public function getUri(): string
    {
        return $this->uri;
    }

    /**
     * Get for a table the human readable name or a fallback.
     *
     * @param string $table Name of table.
     *
     * @return string The human readable name.
     */
    public function getLabel($table): string
    {
        if (strpos($table, 'tl_') !== 0) {
            return $table;
        }
        $shortTable = str_replace('tl_', '', $table);
        $label      = $this->translator->trans('BRD.' . $shortTable, [], 'contao_default');
        if ($label === $shortTable) {
            $shortTable = str_replace('tl_metamodel_', '', $table);
            return ucfirst($shortTable) . ' %s';
        }

        return StringUtil::specialchars($label);
    }

    /**
     * Retrieve the elements.
     *
     * @return array
     */
    public function getElements(): array
    {
        return $this->elements;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\BackendIntegration;

use Doctrine\DBAL\Connection;
use Symfony\Component\Finder\Finder;

/**
 * Handy helper class to retrieve a list of templates.
 */
class TemplateList
{
    /**
     * The database connection.
     *
     * @var Connection
     */
    private $database;

    /**
     * The resource directories.
     *
     * @var string[]
     */
    private $resourceDirs;

    /**
     * The project root directory.
     *
     * @var string
     */
    private $rootDir;

    /**
     * Create a new instance.
     *
     * @param Connection $database     The database connection.
     * @param string[]   $resourceDirs The resource directories.
     * @param string     $rootDir      The root directory.
     */
    public function __construct(Connection $database, $resourceDirs, $rootDir)
    {
        $this->database     = $database;
        $this->resourceDirs = $resourceDirs;
        $this->rootDir      = $rootDir;
    }

    /**
     * Fetch the template group for the detail view of the current MetaModel module.
     *
     * @param string $templateBaseName The base name for the templates to retrieve.
     *
     * @return array
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getTemplatesForBase($templateBaseName)
    {
        $allTemplates = array_replace_recursive(
            $this->fetchTemplatesFromThemes($templateBaseName),
            $this->fetchRootTemplates($templateBaseName),
            $this->fetchTemplatesFromResourceDirectories($templateBaseName)
        );

        $templateList = array();
        foreach ($allTemplates as $template => $themeList) {
            $templateList[$template] = sprintf(
                $GLOBALS['TL_LANG']['MSC']['template_in_theme'],
                $template,
                implode(', ', $themeList)
            );
        }

        ksort($templateList);

        return array_unique($templateList);
    }

    /**
     * Retrieve the message to use when not within a theme (aka global scope).
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getNoThemeMessage()
    {
        return $GLOBALS['TL_LANG']['MSC']['no_theme'];
    }

    /**
     * Fetch the templates from TL_ROOT/templates/.
     *
     * @param string $templateBaseName The base name for the templates to retrieve.
     *
     * @return array
     */
    private function fetchRootTemplates($templateBaseName)
    {
        return $this->getTemplatesForBaseFrom(
            $templateBaseName,
            $this->rootDir . '/templates',
            $this->getNoThemeMessage()
        );
    }

    /**
     * Fetch the templates from TL_ROOT/templates/.
     *
     * @param string $templateBaseName The base name for the templates to retrieve.
     *
     * @return array
     */
    private function fetchTemplatesFromThemes($templateBaseName)
    {
        $allTemplates = [];
        $themes       = $this
            ->database
            ->createQueryBuilder()
            ->select('id,name,templates')
            ->from('tl_theme')
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

        // Add all the theme templates folders.
        foreach ($themes as $theme) {
            $templateDir = $theme['templates'];
            $themeName   = $theme['name'];
            if ($templateDir != '') {
                $allTemplates = array_replace_recursive(
                    $allTemplates,
                    $this->getTemplatesForBaseFrom(
                        $templateBaseName,
                        $this->rootDir . '/' . $templateDir,
                        $themeName
                    )
                );
            }
        }

        return $allTemplates;
    }

    /**
     * Fetch the templates from resource locations.
     *
     * @param string $templateBaseName The base name for the templates to retrieve.
     *
     * @return array
     */
    private function fetchTemplatesFromResourceDirectories($templateBaseName)
    {
        $allTemplates = [];
        $themeName    = $this->getNoThemeMessage();
        // Add the module templates folders if they exist.
        foreach ($this->resourceDirs as $resourceDir) {
            $allTemplates = array_replace_recursive(
                $allTemplates,
                $this->getTemplatesForBaseFrom($templateBaseName, $resourceDir . '/templates', $themeName)
            );
        }

        return $allTemplates;
    }

    /**
     * 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)
    {
        if (!is_dir($folder)) {
            return [];
        }

        $themeName      = trim($themeName);
        $foundTemplates = Finder::create()->in($folder)->name($base . '*');

        $templates = [];
        foreach ($foundTemplates as $template) {
            /** @var \Symfony\Component\Finder\SplFileInfo $template */
            $templates[$template->getBasename('.' . $template->getExtension())] = [$themeName => $themeName];
        }

        return $templates;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     David Maack <david.maack@arcor.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Filter\Rules\Condition;

use MetaModels\Filter\FilterRule;
use MetaModels\Filter\IFilter;

/**
 * This is the MetaModel filter interface.
 */
class ConditionOr extends FilterRule
{
    /**
     * The list of child filters that shall be evaluated.
     *
     * @var IFilter[]
     */
    protected $arrChildFilters = array();

    /**
     * Flag determining if filtering shall return the first non-empty match.
     *
     * This flag determines if the rule shall return the result of the first
     * child that returns at least one element (a return value of NULL from a
     * rule counts as "all ids" in this context and therefore is considered
     * non empty per definition).
     *
     * @var boolean
     */
    protected $stopAfterMatch = false;

    /**
     * Create a new FilterRule instance.
     *
     * @param boolean $stopAfterMatch Flag determining if filtering shall return
     *                                the first non-empty match.
     */
    public function __construct($stopAfterMatch = false)
    {
        parent::__construct();
        $this->stopAfterMatch = $stopAfterMatch;
    }

    /**
     * Adds a child filter to this rule that will get evaluated when this rule is evaluated.
     *
     * @param IFilter $objFilter The filter to add as child.
     *
     * @return ConditionOr
     */
    public function addChild(IFilter $objFilter)
    {
        $this->arrChildFilters[] = $objFilter;

        return $this;
    }

    /**
     * Fetch the ids from all child filter rules.
     *
     * If no entries have been found, the result is an empty array.
     * If no filtering was applied and therefore all ids shall be reported as
     * valid, the return value of NULL is allowed.
     *
     * The OR filter rule has an embedded shortcut for the first rule that
     * returns "null". When this happens, no further child rules will get
     * evaluated, as the result set can not expand any further.
     *
     * Note: when "stopAfterMatch" has been set, the rule will stop processing
     * also when the first rule returns a non empty result and return that
     * result.
     *
     * @return string[]|null
     */
    public function getMatchingIds()
    {
        $arrIds = array();
        foreach ($this->arrChildFilters as $objChildFilter) {
            $arrChildMatches = $objChildFilter->getMatchingIds();
            // NULL => all items - for OR conditions, this can never be more than all so we are already satisfied here.
            if ($arrChildMatches === null) {
                return null;
            }

            if ($arrChildMatches && $this->stopAfterMatch) {
                return $arrChildMatches;
            }

            if ($arrChildMatches) {
                $arrIds = array_merge($arrIds, $arrChildMatches);
            }
        }

        return array_unique($arrIds);
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 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 ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface;

/**
 * This class is the abstract base for the condition builders.
 */
class ConditionBuilderWithoutVariants extends AbstractConditionBuilder
{
    /**
     * The real calculating function.
     *
     * @return void
     *
     * @throws \RuntimeException When the conditions can not be determined yet.
     */
    protected function calculate()
    {
        if ($this->inputScreen['meta']['rendertype'] !== 'standalone') {
            if ($this->container->getBasicDefinition()->getMode() == BasicDefinitionInterface::MODE_HIERARCHICAL) {
                throw new \RuntimeException('Hierarchical mode with parent table is not supported yet.');
            }
        }

        $this->addHierarchicalConditions();
        $this->addParentCondition();
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Stefan Heimes <stefan_heimes@hotmail.com>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Widgets;

use Contao\Widget;

/**
 * Form field "tags", based on form field by Leo Feyer.
 */
class TagsWidget extends Widget
{
    /**
     * Template.
     *
     * @var string
     */
    protected $strTemplate = 'form_widget';

    /**
     * {@inheritDoc}
     */
    public function __set($strKey, $varValue)
    {
        switch ($strKey) {
            case 'maxlength':
                if ($varValue > 0) {
                    $this->arrAttributes['maxlength'] = $varValue;
                }
                break;

            case 'mandatory':
                if ($varValue) {
                    $this->arrAttributes['required'] = 'required';
                } else {
                    unset($this->arrAttributes['required']);
                }
                parent::__set($strKey, $varValue);
                break;

            case 'placeholder':
                $this->arrAttributes['placeholder'] = $varValue;
                break;

            default:
                parent::__set($strKey, $varValue);
                break;
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function validator($varInput)
    {
        if (is_array($varInput)) {
            return parent::validator($varInput);
        }

        return parent::validator(trim($varInput));
    }

    /**
     * Get the css class for an option.
     *
     * @param int $index The sequence number of the current option.
     *
     * @return string
     */
    protected function getClassForOption($index)
    {
        // If true we need another offset.
        $intSub   = ($this->arrConfiguration['includeBlankOption'] ? -1 : 1);
        $strClass = $this->strName;

        if ($index == 0) {
            $strClass .= ' first';
        } elseif ($index === (count($this->options) - $intSub)) {
            $strClass .= ' last';
        }

        if (($index % 2) == 1) {
            $strClass .= ' even';
        } else {
            $strClass .= ' odd';
        }

        return ((strlen($this->strClass)) ? ' ' . $this->strClass : '') . $strClass;
    }

    /**
     * Generate a single checkbox.
     *
     * @param array $val   The value array (needs keys "value" and "label").
     *
     * @param int   $index The sequence number of this option (used for even/odd determination).
     *
     * @return string
     */
    protected function generateOption($val, $index)
    {
        $checked = '';
        if (is_array($this->varValue) && in_array($val['value'], $this->varValue)) {
            $checked = ' checked="checked"';
        }

        return sprintf(
            '' .
            '<input type="checkbox" name="%8$s[]" id="opt_%3$s" class="checkbox" value="%4$s"%5$s%6$s ' .
            '%7$s',
            // @codingStandardsIgnoreStart - Keep the comments.
            $this->getClassForOption($index),             // 1
            $index,                                       // 2
            $this->strName.'_'.$index,                    // 3
            $val['value'],                                // 4
            $checked,                                     // 5
            $this->getAttributes() . $this->strTagEnding, // 6
            $val['label'],                                // 7
            $this->strName                                // 8
            // @codingStandardsIgnoreEnd
        );
    }

    /**
     * {@inheritDoc}
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function generate()
    {
        $return = sprintf(
            '<fieldset id="ctrl_%s" class="checkbox_container">
',
            $this->strName
        );

        $count = 0;

        if ($this->options && is_array($this->options)) {
            if ($this->arrConfiguration['includeBlankOption']) {
                $return .= $this->generateOption(
                    array('value' => '--none--', 'label' => $this->arrConfiguration['blankOptionLabel']),
                    $count++
                );
            }

            // Select all tags.
            $return .= $this->generateOption(
                array('value' => '--all--', 'label' => $GLOBALS['TL_LANG']['metamodels_frontendfilter']['select_all']),
                $count++
            );

            foreach ($this->options as $val) {
                $return .= $this->generateOption($val, $count++);
            }
        } else {
            // Do not filter.
            if ($this->arrConfiguration['includeBlankOption']) {
                $return .= $this->generateOption(
                    array
                    (
                        'value' => '',
                        'label' => $this->arrConfiguration['blankOptionLabel'] .
                            '' .
                            $GLOBALS['TL_LANG']['metamodels_frontendfilter']['no_combinations'] .
                            ''
                    ),
                    $count
                );
            }
        }

        $return .= '</fieldset>';

        return $return;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     David Maack <david.maack@arcor.de>
 * @author     Richard Henkenjohann <richardhenkenjohann@googlemail.com>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels;

/**
 * Interface for a collection of MetaModel items.
 *
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class Items implements IItems
{
    /**
     * Current reading cursor.
     *
     * @var int
     */
    protected $intCursor = -1;

    /**
     * Buffer of contained instances.
     *
     * @var array
     */
    protected $arrItems = array();

    /**
     * Creates a new instance with the passed items.
     *
     * @param array $arrItems The items to be contained in the collection.
     */
    public function __construct($arrItems)
    {
        $this->arrItems = $arrItems;
    }

    /**
     * {@inheritDoc}
     */
    public function rewind()
    {
        $this->first();
    }

    /**
     * {@inheritDoc}
     */
    public function current()
    {
        return $this->getItem();
    }

    /**
     * {@inheritDoc}
     */
    public function key()
    {
        return $this->intCursor;
    }

    /**
     * {@inheritDoc}
     */
    public function valid()
    {
        return ($this->offsetExists($this->intCursor));
    }

    /**
     * {@inheritDoc}
     */
    public function offsetExists($offset)
    {
        if (!is_numeric($offset)) {
            return false;
        }
        return (($this->getCount() > $offset) && ($offset > -1));
    }

    /**
     * {@inheritDoc}
     */
    public function offsetGet($offset)
    {
        if ($this->offsetExists($offset)) {
            return $this->arrItems[$offset];
        }

        return null;
    }

    /**
     * Not implemented in this class.
     *
     * @param mixed $offset The offset to assign the value to.
     *
     * @param mixed $value  The value to set.
     *
     * @return void
     *
     * @throws \RuntimeException Always in this base class.
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function offsetSet($offset, $value)
    {
        throw new \RuntimeException('MetaModelItems is a read only class, you can not manipulate the collection.', 1);
    }

    /**
     * Not implemented in this class.
     *
     * @param mixed $offset The offset to assign the value to.
     *
     * @return void
     *
     * @throws \RuntimeException Always in this base class.
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function offsetUnset($offset)
    {
        throw new \RuntimeException('MetaModelItems is a read only class, you can not manipulate the collection.', 1);
    }

    /**
     * {@inheritdoc}
     */
    public function getItem()
    {
        // Implicitly call first when not within "while ($obj->next())" scope.
        if ($this->intCursor < 0) {
            $this->first();
        }

        // Beyond bounds? return null.
        if (!$this->valid()) {
            return null;
        }

        return $this->arrItems[$this->intCursor];
    }

    /**
     * {@inheritdoc}
     */
    public function getCount()
    {
        return count($this->arrItems);
    }

    /**
     * {@inheritdoc}
     */
    public function first()
    {
        if ($this->getCount() > 0) {
            $this->intCursor = 0;
            return $this;
        }
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function next()
    {
        if ($this->getCount() == $this->intCursor) {
            return false;
        }
        // We must advance over the last element.
        $this->intCursor += 1;

        // Check the index again, see #461.
        return ($this->getCount() == $this->intCursor) ? false : $this;
    }

    /**
     * {@inheritdoc}
     */
    public function prev()
    {
        if ($this->intCursor == 0) {
            return false;
        }

        $this->intCursor--;
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function last()
    {
        $this->intCursor = ($this->getCount() - 1);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function reset()
    {
        $this->intCursor = -1;
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getClass()
    {
        $arrClass = array();
        if ($this->intCursor == 0) {
            $arrClass[] = 'first';
        }

        if ($this->intCursor == ($this->getCount() - 1)) {
            $arrClass[] = 'last';
        }

        if (($this->intCursor % 2) == 0) {
            $arrClass[] = 'even';
        } else {
            $arrClass[] = 'odd';
        }
        return implode(' ', $arrClass);
    }

    /**
     * {@inheritdoc}
     */
    public function parseValue($strOutputFormat = 'text', $objSettings = null)
    {
        return $this->getItem()->parseValue($strOutputFormat, $objSettings);
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
     */
    public function parseAll($strOutputFormat = 'text', $objSettings = null)
    {
        $arrResult = array();

        // Buffer cursor.
        $intCursor = $this->intCursor;

        foreach ($this as $objItem) {
            $arrParsedItem = $this->parseValue($strOutputFormat, $objSettings);

            $arrParsedItem['class'] .= ' ' . $this->getClass();
            $arrParsedItem['class']  = trim($arrParsedItem['class']);

            $arrResult[] = $arrParsedItem;
        }

        // Restore cursor.
        $this->intCursor = $intCursor;

        return $arrResult;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     David Molineus <david.molineus@netzmacht.de>
 * @author     Alexander Menk <a.menk@imi.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral;

use ContaoCommunityAlliance\DcGeneral\Contao\DataDefinition\Definition\Contao2BackendViewDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetParentHeaderEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ModelToLabelEvent;
use ContaoCommunityAlliance\DcGeneral\View\Event\RenderReadablePropertyValueEvent;
use MetaModels\DcGeneral\Data\Model;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\IItem;
use MetaModels\Items;
use MetaModels\Render\Setting\ICollection;
use MetaModels\Render\Setting\IRenderSettingFactory;
use MetaModels\Render\Template;

/**
 * Render a MetaModel item in the backend using the render setting attached to the active input screen.
 */
class ItemRendererListener
{
    /**
     * The render setting factory.
     *
     * @var IRenderSettingFactory
     */
    private $renderSettingFactory;

    /**
     * Create a new instance.
     *
     * @param IRenderSettingFactory $renderSettingFactory The render setting factory.
     */
    public function __construct(IRenderSettingFactory $renderSettingFactory)
    {
        $this->renderSettingFactory = $renderSettingFactory;
    }

    /**
     * Render the current item using the specified render setting.
     *
     * @param ModelToLabelEvent $event The event.
     *
     * @return void
     */
    public function render(ModelToLabelEvent $event)
    {
        $environment = $event->getEnvironment();
        /** @var IMetaModelDataDefinition $definition */
        $definition = $environment->getDataDefinition();
        /** @var Contao2BackendViewDefinitionInterface $viewSection */
        $viewSection = $definition->getDefinition(Contao2BackendViewDefinitionInterface::NAME);
        $listing     = $viewSection->getListingConfig();

        /** @var Model $model */
        $model = $event->getModel();

        if (!($model instanceof Model)) {
            return;
        }

        $nativeItem = $model->getItem();
        $metaModel  = $nativeItem->getMetaModel();

        $renderSetting = $this->renderSettingFactory
            ->createCollection($metaModel, $definition->getMetaModelDefinition()->getActiveRenderSetting());

        if (!$renderSetting) {
            return;
        }

        $data = array($nativeItem->parseValue('html5', $renderSetting));

        if ($listing->getShowColumns()) {
            $event->setArgs($data[0]['html5']);
            return;
        }

        $template      = new Template($renderSetting->get('template'));
        $renderSetting = self::removeInvariantAttributes($nativeItem, $renderSetting);

        $template->setData(
            array(
                'settings' => $renderSetting,
                'items'    => new Items(array($nativeItem)),
                'view'     => $renderSetting,
                'data'     => $data
            )
        );

        $event->setLabel('%s')->setArgs(array($template->parse('html5')));
    }

    /**
     * Render a model for use in a group header.
     *
     * @param RenderReadablePropertyValueEvent $event The event.
     *
     * @return void
     */
    public function getReadableValue(RenderReadablePropertyValueEvent $event)
    {
        $environment = $event->getEnvironment();
        /** @var IMetaModelDataDefinition $definition */
        $definition = $environment->getDataDefinition();

        /** @var Model $model */
        $model = $event->getModel();

        if (!($model instanceof Model)) {
            return;
        }

        $nativeItem = $model->getItem();
        $metaModel  = $nativeItem->getMetaModel();

        $renderSetting = $this->renderSettingFactory->createCollection(
            $metaModel,
            $definition->getMetaModelDefinition()->getActiveRenderSetting()
        );

        if (!$renderSetting) {
            return;
        }

        $result = $nativeItem->parseAttribute($event->getProperty()->getName(), 'text', $renderSetting);

        if (!isset($result['text'])) {
            $event->setRendered(
                sprintf(
                    'Unexpected behaviour, attribute %s text representation was not rendered.',
                    $event->getProperty()->getName()
                )
            );
            return;
        }

        $event->setRendered($result['text']);
    }

    /**
     * Add additional parent header fields.
     *
     * @param GetParentHeaderEvent $event The subscribed event.
     *
     * @return void
     */
    public function addAdditionalParentHeaderFields(GetParentHeaderEvent $event)
    {
        $parentModel = $event->getModel();

        if (!$parentModel instanceof Model) {
            return;
        }
        $environment = $event->getEnvironment();
        /** @var IMetaModelDataDefinition $definition */
        $definition = $environment->getDataDefinition();

        $item          = $parentModel->getItem();
        $metaModel     = $item->getMetaModel();
        $renderSetting = $this->renderSettingFactory->createCollection(
            $metaModel,
            $definition->getMetaModelDefinition()->getActiveRenderSetting()
        );
        $additional    = array();

        foreach ($renderSetting->getSettingNames() as $name) {
            $parsed = $item->parseAttribute($name, 'text', $renderSetting);
            $name   = $item->getAttribute($name)->getName();

            $additional[$name] = $parsed['text'];
        }

        $additional = array_merge(
            $additional,
            $event->getAdditional()
        );

        $event->setAdditional($additional);
    }

    /**
     * Remove invariant attributes from the render setting.
     *
     * This is done by cloning the input collection of render settings and removing any invariant attribute.
     *
     * @param IItem       $nativeItem    The native item.
     *
     * @param ICollection $renderSetting The render setting to be used.
     *
     * @return ICollection
     */
    private function removeInvariantAttributes(IItem $nativeItem, ICollection $renderSetting)
    {
        $model = $nativeItem->getMetaModel();

        if ($model->hasVariants() && !$nativeItem->isVariantBase()) {
            // Create a clone to have a separate copy of the object as we are going to manipulate it here.
            $renderSetting = clone $renderSetting;

            // Loop over all attributes and remove those from rendering that are not desired.
            foreach (array_keys($model->getInVariantAttributes()) as $strAttrName) {
                $renderSetting->setSetting($strAttrName, null);
            }
        }

        return $renderSetting;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\RenderSettings;

use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\BuildWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\DecodePropertyValueForWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use Doctrine\DBAL\Connection;
use MetaModels\IFactory;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * This handles the rendering of models to labels.
 */
class JumpToListener extends AbstractAbstainingListener
{
    /**
     * The MetaModel factory.
     *
     * @var IFactory
     */
    private $factory;

    /**
     * The connection.
     *
     * @var Connection
     */
    private $connection;

    /**
     * The translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param IFactory                 $factory           The factory.
     * @param Connection               $connection        The database connection.
     * @param TranslatorInterface      $translator        The translator.
     */
    public function __construct(
        RequestScopeDeterminator $scopeDeterminator,
        IFactory $factory,
        Connection $connection,
        TranslatorInterface $translator
    ) {
        parent::__construct($scopeDeterminator);
        $this->factory    = $factory;
        $this->connection = $connection;
        $this->translator = $translator;
    }

    /**
     * Translates the values of the jumpTo entries into the real array.
     *
     * @param DecodePropertyValueForWidgetEvent $event The event.
     *
     * @return void
     */
    public function decodeValue(DecodePropertyValueForWidgetEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getProperty() !== 'jumpTo')) {
            return;
        }

        $propInfo = $event->getEnvironment()->getDataDefinition()->getPropertiesDefinition()->getProperty('jumpTo');
        $value    = StringUtil::deserialize($event->getValue(), true);
        $extra    = $propInfo->getExtra();

        $newValues = [];
        $languages = $extra['columnFields']['langcode']['options'];
        foreach (array_keys($languages) as $key) {
            $newValue = '';
            $filter   = 0;
            if ($value) {
                foreach ($value as $arr) {
                    if (!is_array($arr)) {
                        break;
                    }

                    // Set the new value and exit the loop.
                    if (array_search($key, $arr) !== false) {
                        $newValue = '{{link_url::' . $arr['value'] . '}}';
                        $filter   = $arr['filter'];
                        break;
                    }
                }
            }

            // Build the new array.
            $newValues[] = [
                'langcode' => $key,
                'value'    => $newValue,
                'filter'   => $filter
            ];
        }

        $event->setValue($newValues);
    }

    /**
     * Translates the values of the jumpTo entries into the internal array.
     *
     * @param EncodePropertyValueFromWidgetEvent $event The event.
     *
     * @return void
     */
    public function encodeValue(EncodePropertyValueFromWidgetEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getProperty() !== 'jumpTo')) {
            return;
        }

        $value = StringUtil::deserialize($event->getValue(), true);

        foreach ($value as $k => $v) {
            $value[$k]['value'] = str_replace(['{{link_url::', '}}'], ['', ''], $v['value']);
        }

        $event->setValue(serialize($value));
    }

    /**
     * Provide options for template selection.
     *
     * @param BuildWidgetEvent $event The event.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function buildWidget(BuildWidgetEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getProperty()->getName() !== 'jumpTo')) {
            return;
        }

        $model     = $event->getModel();
        $metaModel =
            $this->factory->getMetaModel($this->factory->translateIdToMetaModelName($model->getProperty('pid')));

        $extra = $event->getProperty()->getExtra();

        if ($metaModel->isTranslated()) {
            $arrLanguages = [];
            foreach ((array) $metaModel->getAvailableLanguages() as $strLangCode) {
                $arrLanguages[$strLangCode] = $this->translator
                    ->trans('LNG.'. $strLangCode, [], 'contao_languages');
            }
            asort($arrLanguages);

            $extra['minCount'] = count($arrLanguages);
            $extra['maxCount'] = count($arrLanguages);

            $extra['columnFields']['langcode']['options'] = $arrLanguages;
        } else {
            $extra['minCount'] = 1;
            $extra['maxCount'] = 1;

            $extra['columnFields']['langcode']['options'] = [
                'xx' => $this->translator
                    ->trans(
                        'tl_metamodel_rendersettings.jumpTo_allLanguages',
                        [],
                        'contao_tl_metamodel_rendersettings'
                    )
            ];
        }

        $extra['columnFields']['filter']['options'] = $this->getFilterSettings($model);

        $event->getProperty()->setExtra($extra);
    }

    /**
     * Retrieve the model filters for the MCW.
     *
     * @param ModelInterface $model The model containing the currently edited render setting.
     *
     * @return array
     */
    private function getFilterSettings(ModelInterface $model)
    {
        $filters = $this->connection
            ->createQueryBuilder()
            ->select('id', 'name')
            ->from('tl_metamodel_filter')
            ->where('pid=:id')
            ->setParameter('id', $model->getProperty('pid'))
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

        $result = [];
        foreach ($filters as $filter) {
            $result[$filter['id']] = $filter['name'];
        }

        return $result;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     David Molineus <david.molineus@netzmacht.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\ViewCombination;

use Contao\StringUtil;
use Doctrine\DBAL\Connection;
use MetaModels\IFactory;
use MetaModels\IMetaModel;

/**
 * This class obtains information from the database about input screens.
 */
class InputScreenInformationBuilder
{
    /**
     * The database connection.
     *
     * @var Connection
     */
    private $connection;

    /**
     * The MetaModels factory.
     *
     * @var IFactory
     */
    private $factory;

    /**
     * Create a new instance.
     *
     * @param Connection $connection The database connection.
     * @param IFactory   $factory    The MetaModels factory.
     */
    public function __construct(Connection $connection, IFactory $factory)
    {
        $this->connection = $connection;
        $this->factory    = $factory;
    }

    /**
     * Fetch information about an input screen.
     *
     * @param array $idList The ids of the input screens to obtain (table name => id).
     *
     * @return array
     */
    public function fetchInputScreens($idList): array
    {
        $idList  = array_filter($idList);
        $builder = $this->connection->createQueryBuilder();
        $screens = $builder
            ->select('d.*')
            ->from('tl_metamodel_dca', 'd')
            ->leftJoin('d', 'tl_metamodel', 'm', 'm.id=d.pid')
            ->where($builder->expr()->in('d.id', ':idList'))
            ->setParameter('idList', $idList, Connection::PARAM_STR_ARRAY)
            ->orderBy('m.sorting')
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

        $result = [];
        $keys   = array_flip($idList);
        foreach ($screens as $screen) {
            $metaModelName          = $keys[$screen['id']];
            $result[$metaModelName] = $this->prepareInputScreen($metaModelName, $screen);
        }

        return $result;
    }

    /**
     * Prepare the input screen data.
     *
     * @param string $modelName The MetaModel name.
     * @param array  $screen    The screen meta data.
     *
     * @return array
     *
     * @throws \InvalidArgumentException When the MetaModel can not be retrieved.
     */
    private function prepareInputScreen($modelName, $screen): array
    {
        if (null === $metaModel = $this->factory->getMetaModel($modelName)) {
            throw new \InvalidArgumentException('Could not retrieve MetaModel ' . $modelName);
        }
        $caption     = ['' => $metaModel->getName()];
        $description = ['' => $metaModel->getName()];
        foreach (StringUtil::deserialize($screen['backendcaption'], true) as $languageEntry) {
            $langCode               = $languageEntry['langcode'];
            $caption[$langCode]     = !empty($label = $languageEntry['label']) ? $label : $caption[''];
            $description[$langCode] = !empty($title = $languageEntry['description']) ? $title : $description[''];
            if ($metaModel->getFallbackLanguage() === $langCode) {
                $caption['']     = $label;
                $description[''] = $title;
            }
        }

        $result = [
            'meta'        => $screen,
            'properties'  => $this->fetchPropertiesFor($screen['id'], $metaModel),
            'conditions'  => $this->fetchConditions($screen['id']),
            'groupSort'   => $this->fetchGroupSort($screen['id'], $metaModel),
            'label'       => $caption,
            'description' => $description
        ];

        $bySetting         = $this->buildConditionTree($result['conditions']);
        $result['legends'] = $this->convertLegends($result['properties'], $metaModel, $bySetting);

        return $result;
    }

    /**
     * Build condition tree.
     *
     * @param array $conditions The condition information.
     *
     * @return array
     */
    private function buildConditionTree(array $conditions): array
    {
        // Build condition tree.
        $conditionMap = [];
        $bySetting    = [];
        foreach ($conditions as $condition) {
            unset($converted);
            // Check if already mapped, if so, we need to set the values.
            if (array_key_exists($condition['id'], $conditionMap)) {
                $converted = &$conditionMap[$condition['id']];
                foreach ($condition as $key => $value) {
                    $converted[$key] = $value;
                }
            } else {
                $converted                      = \array_slice($condition, 0);
                $conditionMap[$condition['id']] = &$converted;
            }
            // Is on root level - add to setting now.
            if (empty($condition['pid'])) {
                $bySetting[$condition['settingId']][] = &$converted;
                continue;
            }
            // Is a child, check if parent already added.
            if (!isset($conditionMap[$condition['pid']])) {
                $temp                            = ['children' => []];
                $conditionMap[$condition['pid']] = &$temp;
            }
            // Add child to parent now.
            $conditionMap[$condition['pid']]['children'][] = &$converted;
        }

        return $bySetting;
    }

    /**
     * Fetch all properties for the passed input screen.
     *
     * @param string     $inputScreenId The input screen to obtain properties for.
     * @param IMetaModel $metaModel     The MetaModel to fetch properties for.
     *
     * @return array
     */
    private function fetchPropertiesFor($inputScreenId, IMetaModel $metaModel): array
    {
        $builder = $this->connection->createQueryBuilder();
        return array_map(function ($column) use ($inputScreenId, $metaModel) {
            if ('attribute' !== $column['dcatype']) {
                return $column;
            }
            if (!($attribute = $metaModel->getAttributeById($column['attr_id']))) {
                // @codingStandardsIgnoreStart
                @trigger_error(
                    'Unknown attribute "' . $column['attr_id'] . '" in input screen "' . $inputScreenId . '"',
                    E_USER_WARNING
                );
                // @codingStandardsIgnoreEnd
                return $column;
            }
            $column = array_merge(
                $column,
                $attribute->getFieldDefinition($column),
                ['col_name' => $attribute->getColName()]
            );

            return $column;
        }, $builder
            ->select('*')
            ->from('tl_metamodel_dcasetting')
            ->where('pid=:pid')
            ->andWhere('published=:published')
            ->setParameter('pid', $inputScreenId)
            ->setParameter('published', 1)
            ->orderBy('sorting')
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC));
    }

    /**
     * Fetch conditions for an input screen.
     *
     * @param string $inputScreenId The input screen to obtain conditions for.
     *
     * @return array
     */
    private function fetchConditions($inputScreenId): array
    {
        $builder = $this->connection->createQueryBuilder();

        return $builder
            ->select('cond.*', 'setting.attr_id AS setting_attr_id')
            ->from('tl_metamodel_dcasetting_condition', 'cond')
            ->leftJoin('cond', 'tl_metamodel_dcasetting', 'setting', 'cond.settingId=setting.id')
            ->leftJoin('setting', 'tl_metamodel_dca', 'dca', 'setting.pid=dca.id')
            ->where('cond.enabled=1')
            ->andWhere('setting.published=1')
            ->andWhere('dca.id=:screenId')
            ->setParameter('screenId', $inputScreenId)
            ->orderBy('pid')
            ->addOrderBy('sorting')
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);
    }

    /**
     * Fetch groupings for an input screen.
     *
     * @param string     $inputScreenId The input screen to obtain properties for.
     * @param IMetaModel $metaModel     The MetaModel to fetch properties for.
     *
     * @return array
     */
    private function fetchGroupSort($inputScreenId, IMetaModel $metaModel): array
    {
        $builder = $this->connection->createQueryBuilder();

        return array_map(function ($information) use ($inputScreenId, $metaModel) {
            $information['isdefault']      = (bool) $information['isdefault'];
            $information['ismanualsort']   = (bool) $information['ismanualsort'];
            $information['rendergrouplen'] = (int) $information['rendergrouplen'];
            if ($information['ismanualsort']) {
                $information['rendergrouptype'] = 'none';
            }

            if (!empty($information['rendersortattr'])) {
                if (!($attribute = $metaModel->getAttributeById($information['rendersortattr']))) {
                    // @codingStandardsIgnoreStart
                    @trigger_error(
                        sprintf(
                            'Unknown attribute "%1$s" in group sorting "%2$s.%3$s"',
                            $information['rendersortattr'],
                            $inputScreenId,
                            $information['id']
                        ),
                        E_USER_WARNING
                    );
                    // @codingStandardsIgnoreEnd
                    return $information;
                }
                $information['col_name'] = $attribute->getColName();
            }

            return $information;
        }, $builder
            ->select('*')
            ->from('tl_metamodel_dca_sortgroup')
            ->where('pid=:screenId')
            ->setParameter('screenId', $inputScreenId)
            ->orderBy('sorting')
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC));
    }

    /**
     * Convert property list to legend list.
     *
     * @param array      $properties The property and legend information.
     * @param IMetaModel $metaModel  The MetaModel to fetch properties for.
     *
     * @param array      $conditions The conditions for the entries.
     *
     * @return array
     */
    private function convertLegends(array $properties, IMetaModel $metaModel, array $conditions): array
    {
        $result = [];
        $label  = [];
        if ($trans = $metaModel->isTranslated()) {
            foreach ($metaModel->getAvailableLanguages() as $availableLanguage) {
                $label[$availableLanguage] = $metaModel->getName();
            }
        } else {
            $label[$metaModel->getActiveLanguage()] = $metaModel->getName();
        }

        $legend = [
            'label'      => $label,
            'hide'       => false,
            'properties' => []
        ];

        $condition = function ($property) use ($conditions) {
            if (!isset($conditions[$property['id']])) {
                return null;
            }

            return [
                'type'     => 'conditionand',
                'children' => $conditions[$property['id']]
            ];
        };

        foreach ($properties as $property) {
            switch ($property['dcatype']) {
                case 'legend':
                    $this->convertLegend($property, $trans, $condition, $legend, $result);
                    break;
                case 'attribute':
                    $this->convertAttribute($property, $condition, $legend);
                    break;
                default:
                    break;
            }
        }
        if (!empty($legend['properties'])) {
            $result['legend' . (\count($result) + 1)] = $legend;
        }

        return $result;
    }

    /**
     * Convert a legend property.
     *
     * @param array    $property  The property information to convert.
     * @param bool     $trans     Flag if the MetaModel is translated.
     * @param callable $condition The condition transformer.
     * @param array    $legend    The current legend information.
     * @param array    $result    The resulting information.
     *
     * @return void
     */
    private function convertLegend(array $property, bool $trans, $condition, array &$legend, array &$result)
    {
        if (!empty($legend['properties'])) {
            $result['legend' . (\count($result) + 1)] = $legend;
        }
        $legend = [
            'label'      => $trans
                ? unserialize($property['legendtitle'], ['allowed_classes' => false])
                : ['' => $property['legendtitle']],
            'hide'       => (bool) $property['legendhide'],
            'properties' => [],
            'condition' => $condition($property)
        ];
    }

    /**
     * Convert an attribute property.
     *
     * @param array    $property  The property information to convert.
     * @param callable $condition The condition transformer.
     * @param array    $legend    The current legend information.
     *
     * @return void
     */
    private function convertAttribute(array $property, $condition, array &$legend)
    {
        if (!isset($property['col_name'])) {
            return;
        }
        $legend['properties'][] = [
            'name'       => $property['col_name'],
            'condition' => $condition($property)
        ];
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\DcaSetting;

use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ModelToLabelEvent;
use Doctrine\DBAL\Connection;
use MetaModels\Attribute\IAttributeFactory;
use MetaModels\CoreBundle\Assets\IconBuilder;
use MetaModels\IFactory;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * This handles the rendering of models to labels.
 */
class ModelToLabelListener extends AbstractListener
{
    /**
     * The attribute factory.
     *
     * @var IAttributeFactory
     */
    private $attributeFactory;

    /**
     * The icon builder.
     *
     * @var IconBuilder
     */
    private $iconBuilder;

    /**
     * The translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param IFactory                 $factory           The MetaModel factory.
     * @param Connection               $connection        The database connection.
     * @param IAttributeFactory        $attributeFactory  The attribute factory.
     * @param IconBuilder              $iconBuilder       The icon builder.
     * @param TranslatorInterface      $translator        The translator.
     */
    public function __construct(
        RequestScopeDeterminator $scopeDeterminator,
        IFactory $factory,
        Connection $connection,
        IAttributeFactory $attributeFactory,
        IconBuilder $iconBuilder,
        TranslatorInterface $translator
    ) {
        parent::__construct($scopeDeterminator, $factory, $connection);
        $this->attributeFactory = $attributeFactory;
        $this->iconBuilder      = $iconBuilder;
        $this->translator       = $translator;
    }

    /**
     * Render the html for the input screen condition.
     *
     * @param ModelToLabelEvent $event The event.
     *
     * @return void
     */
    public function handle(ModelToLabelEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $model = $event->getModel();

        switch ($model->getProperty('dcatype')) {
            case 'attribute':
                $this->drawAttribute($event);
                break;

            case 'legend':
                $this->drawLegend($event);
                break;

            default:
                break;
        }
    }

    /**
     * Draw the input screen setting.
     *
     * @param ModelToLabelEvent $event The event.
     *
     * @return void
     */
    private function drawAttribute(ModelToLabelEvent $event)
    {
        $model     = $event->getModel();
        $metaModel = $this->getMetaModelFromModel($model);
        $attribute = $metaModel->getAttributeById($model->getProperty('attr_id'));

        if ($attribute) {
            $type     = $attribute->get('type');
            $image    = $this->iconBuilder->getBackendIconImageTag(
                $this->attributeFactory->getIconForType($type),
                $type,
                '',
                'bundles/metamodelscore/images/icons/fields.png'
            );
            $name     = $attribute->getName();
            $colName  = $attribute->getColName();
            $isUnique = $attribute->get('isunique');
        } else {
            $type     = 'unknown ID: ' . $model->getProperty('attr_id');
            $image    = $this->iconBuilder->getBackendIconImageTag('bundles/metamodelscore/images/icons/fields.png');
            $name     = 'unknown attribute';
            $colName  = 'unknown column';
            $isUnique = false;
        }

        $event
            ->setLabel('
%s [%s]

                    %s%s%s %s

')
            ->setArgs([
                $model->getProperty('published') ? 'published' : 'unpublished',
                $colName,
                $type,
                $image,
                $name,
                // unique attributes are automatically mandatory
                $model->getProperty('mandatory') || $isUnique
                    ? ' ['. $this->trans('mandatory.0') . ']'
                    : '',
                $model->getProperty('tl_class') ? sprintf('[%s]', $model->getProperty('tl_class')) : ''
            ]);
    }

    /**
     * Draw a legend.
     *
     * @param ModelToLabelEvent $event The event.
     *
     * @return void
     */
    private function drawLegend(ModelToLabelEvent $event)
    {
        $model     = $event->getModel();
        $metaModel = $this->getMetaModelFromModel($model);
        if (is_array($legend = StringUtil::deserialize($model->getProperty('legendtitle')))) {
            foreach ([$metaModel->getActiveLanguage(), $metaModel->getFallbackLanguage()] as $language) {
                if (array_key_exists($language, $legend) && !empty($legend[$language])) {
                    $legend = $legend[$language];
                    break;
                }
            }
        }
        if (empty($legend)) {
            $legend = 'legend';
        }

        $event
            ->setLabel('
%s

%s%s
')
            ->setArgs([
                $model->getProperty('published') ? 'published' : 'unpublished',
                $this->trans('dcatypes.legend'),
                $legend,
                $model->getProperty('legendhide') ? ':hide' : ''
            ]);
    }

    /**
     * Translate a key.
     *
     * @param string $key The key to translate.
     *
     * @return string
     */
    private function trans($key)
    {
        return $this->translator->trans('tl_metamodel_dcasetting.' . $key, [], 'contao_tl_metamodel_dcasetting');
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\EnvironmentPopulator;

use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\DcGeneral\Factory\Event\PopulateEnvironmentEvent;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;

/**
 * This trait delegates the populate handling to the populate() method if the environment belongs to a MetaModel.
 */
trait MetaModelPopulatorTrait
{
    /**
     * Handle the event.
     *
     * @param PopulateEnvironmentEvent $event The event.
     *
     * @return void
     */
    public function handle(PopulateEnvironmentEvent $event)
    {
        $environment = $event->getEnvironment();
        if ($environment->getDataDefinition() instanceof IMetaModelDataDefinition) {
            $this->populate($environment);
        }
    }

    /**
     * Populate the environment.
     *
     * @param EnvironmentInterface $environment The environment.
     *
     * @return void
     */
    abstract protected function populate(EnvironmentInterface $environment);
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Exceptions;

/**
 * This class compares two variables and emits exceptions when they differ.
 */
class DifferentValuesException extends \Exception
{
    /**
     * Two values are an array but differ in count.
     */
    const ARRAY_COUNT_MISMATCH = 1;

    /**
     * Two values are an array but differ in keys.
     */
    const ARRAY_KEY_MISMATCH = 1;

    /**
     * Two values are an array but have a different value at a certain key.
     */
    const ARRAY_VALUE_MISMATCH = 1;

    /**
     * Two values differ in type.
     */
    const TYPE_MISMATCH = 1;

    /**
     * Two values are different.
     */
    const VALUE_MISMATCH = 1;

    /**
     * The expected value.
     *
     * @var mixed
     */
    private $expected;

    /**
     * The actual value.
     *
     * @var mixed
     */
    private $actual;

    /**
     * Run in strict mode.
     *
     * @var bool
     */
    private $strict;

    /**
     * Create a new instance.
     *
     * @param mixed      $expected The expected value.
     *
     * @param mixed      $actual   The actual value.
     *
     * @param bool       $strict   Compared in strict mode.
     *
     * @param string     $message  The Exception message to throw.
     *
     * @param int        $code     The Exception code.
     *
     * @param \Exception $previous The previous exception used for the exception chaining.
     */
    public function __construct($expected, $actual, $strict, $message = '', $code = 0, \Exception $previous = null)
    {
        parent::__construct(
            $message,
            $code,
            $previous
        );

        $this->expected = $expected;
        $this->actual   = $actual;
        $this->strict   = $strict;
    }

    /**
     * Check if the actual argument is the same as the expected.
     *
     * @param mixed $expected The expected value.
     *
     * @param mixed $actual   The actual value.
     *
     * @param bool  $strict   Run in strict mode.
     *
     * @return void
     *
     * @throws DifferentValuesException When the values differ.
     */
    public static function compare($expected, $actual, $strict = true)
    {
        try {
            self::calculateDiff($expected, $actual, $strict);
        } catch (\Exception $exception) {
            $instance = new DifferentValuesException(
                $expected,
                $actual,
                $strict,
                'The values differ.',
                0,
                $exception
            );
            throw $instance;
        }
    }

    /**
     * Retrieve the value that was expected.
     *
     * @return mixed
     */
    public function getExpected()
    {
        return $this->expected;
    }

    /**
     * Retrieve the value that was actual encountered.
     *
     * @return mixed
     */
    public function getActual()
    {
        return $this->actual;
    }

    /**
     * Retrieve the strict mode flag.
     *
     * @return boolean
     */
    public function isStrict()
    {
        return $this->strict;
    }

    /**
     * Retrieve the messages of all exceptions as chain.
     *
     * @param string $glue The glue to use to combine all exception messages.
     *
     * @return string
     */
    public function getLongMessage($glue = ' ')
    {
        $messages  = array();
        $exception = $this;
        do {
            $messages[] = $exception->getMessage();
        } while (null !== ($exception = $exception->getPrevious()));

        return implode($glue, $messages);
    }

    /**
     * Check if the actual argument is of type array and empty and the expected value is of type string and also empty.
     *
     * @param mixed $expected The expected value.
     *
     * @param mixed $actual   The actual value.
     *
     * @return bool
     */
    private static function isEmptyArrayEquivalent($expected, $actual)
    {
        return (gettype($expected) == 'string')
            && ((gettype($actual) == 'array') || (gettype($actual) == 'NULL'))
            && empty($actual)
            && empty($expected);
    }

    /**
     * Check for differences in arrays.
     *
     * @param array $expected The expected value.
     *
     * @param array $actual   The actual value.
     *
     * @param bool  $strict   Run in strict mode.
     *
     * @return void
     *
     * @throws \LogicException When the values differ.
     */
    private static function calculateArrayDiff($expected, $actual, $strict)
    {
        if (count($expected) !== count($actual)) {
            throw new \LogicException(
                sprintf(
                    'Array element count mismatch. Found %s, expected %s.',
                    count($actual),
                    count($expected)
                ),
                self::ARRAY_COUNT_MISMATCH
            );
        }

        reset($actual);
        foreach ($expected as $key => $value) {
            if ($key !== key($actual)) {
                throw new \LogicException(
                    sprintf(
                        'Array key mismatch. Found %s, expected %s.',
                        key($actual),
                        $key
                    ),
                    self::ARRAY_KEY_MISMATCH
                );
            }

            try {
                self::calculateDiff($value, current($actual), $strict);
            } catch (\Exception $exception) {
                throw new \LogicException(
                    sprintf(
                        'Array value mismatch for key %s.',
                        key($actual)
                    ),
                    self::ARRAY_VALUE_MISMATCH,
                    $exception
                );
            }
            next($actual);
        }
    }

    /**
     * Helper to determine if two values are the same.
     *
     * @param mixed $expected The expected value.
     *
     * @param mixed $actual   The actual value.
     *
     * @param bool  $strict   Run in strict mode.
     *
     * @return void
     *
     * @throws \LogicException When the values differ.
     */
    private static function calculateDiff($expected, $actual, $strict)
    {
        if ($expected === $actual) {
            return;
        }

        if (gettype($expected) !== gettype($actual)) {
            // Only exception of the rule: array values are transported as empty string.
            if (!$strict && self::isEmptyArrayEquivalent($expected, $actual)) {
                return;
            }

            throw new \LogicException(
                sprintf(
                    'Encountered type %s expected %s (Found %s, expected %s)',
                    gettype($actual),
                    gettype($expected),
                    var_export($actual, true),
                    var_export($expected, true)
                ),
                self::TYPE_MISMATCH
            );
        }

        if (is_array($expected)) {
            self::calculateArrayDiff($expected, $actual, $strict);
        }

        throw new \LogicException(
            sprintf(
                'Found %s expected %s',
                var_export($actual, true),
                var_export($expected, true)
            ),
            self::VALUE_MISMATCH
        );
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     David Maack <david.maack@arcor.de>
 * @author     Stefan Heimes <stefan_heimes@hotmail.com>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Filter\Setting;

use MetaModels\FrontendIntegration\FrontendFilterOptions;
use MetaModels\IItem;
use MetaModels\Render\Setting\ICollection as IRenderSettings;

/**
 * Base implementation for settings that can contain children.
 */
abstract class WithChildren extends Simple implements IWithChildren
{
    /**
     * All child settings embedded in this setting.
     *
     * @var ISimple[]
     */
    protected $arrChildren = array();

    /**
     * {@inheritdoc}
     */
    public function addChild(ISimple $objFilterSetting)
    {
        $this->arrChildren[] = $objFilterSetting;
    }

    /**
     * {@inheritdoc}
     */
    public function generateFilterUrlFrom(IItem $objItem, IRenderSettings $objRenderSetting)
    {
        $arrFilterUrl = array();
        foreach ($this->arrChildren as $objSetting) {
            $arrFilterUrl = array_merge($arrFilterUrl, $objSetting->generateFilterUrlFrom($objItem, $objRenderSetting));
        }
        return $arrFilterUrl;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameters()
    {
        $arrParams = array();
        foreach ($this->arrChildren as $objSetting) {
            $arrParams = array_merge($arrParams, $objSetting->getParameters());
        }
        return $arrParams;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterDCA()
    {
        $arrParams = array();
        foreach ($this->arrChildren as $objSetting) {
            $arrParams = array_merge($arrParams, $objSetting->getParameterDCA());
        }
        return $arrParams;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterFilterWidgets(
        $arrIds,
        $arrFilterUrl,
        $arrJumpTo,
        FrontendFilterOptions $objFrontendFilterOptions
    ) {
        $arrParams = array();
        foreach ($this->arrChildren as $objSetting) {
            $arrParams = array_merge(
                $arrParams,
                $objSetting->getParameterFilterWidgets($arrIds, $arrFilterUrl, $arrJumpTo, $objFrontendFilterOptions)
            );
        }
        return $arrParams;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterFilterNames()
    {
        $arrParams = array();
        foreach ($this->arrChildren as $objSetting) {
            $arrParams = array_merge($arrParams, $objSetting->getParameterFilterNames());
        }
        return $arrParams;
    }

    /**
     * Retrieve a list of all referenced attributes within the filter setting.
     *
     * @return array
     */
    public function getReferencedAttributes()
    {
        $arrAttributes = array();
        foreach ($this->arrChildren as $objSetting) {
            $arrAttributes = array_merge($arrAttributes, $objSetting->getReferencedAttributes());
        }
        return $arrAttributes;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @author     Richard Henkenjohann <richardhenkenjohann@googlemail.com>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\DcaSetting;

use Contao\Message;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\BuildWidgetEvent;
use Doctrine\DBAL\Connection;
use MetaModels\IFactory;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * This handles disabling of the mandatory field.
 */
class DisableMandatoryListener extends AbstractListener
{
    /**
     * The translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param IFactory                 $factory           The MetaModel factory.
     * @param Connection               $connection        The database connection.
     * @param TranslatorInterface      $translator        The translator.
     */
    public function __construct(
        RequestScopeDeterminator $scopeDeterminator,
        IFactory $factory,
        Connection $connection,
        TranslatorInterface $translator
    ) {
        parent::__construct($scopeDeterminator, $factory, $connection);
        $this->translator = $translator;
    }

    /**
     * Disable the mandatory checkbox field if the selected attribute is unique.
     *
     * @param BuildWidgetEvent $event The event.
     *
     * @return void
     */
    public function handle(BuildWidgetEvent $event)
    {
        $environment = $event->getEnvironment();
        if (($environment->getDataDefinition()->getName() !== 'tl_metamodel_dcasetting')
            || ($event->getProperty()->getName() !== 'mandatory')
            || (null === $event->getModel()->getId())) {
            return;
        }

        $model     = $event->getModel();
        $metaModel = $this->getMetaModelFromModel($model);
        $attribute = $metaModel->getAttributeById($model->getProperty('attr_id'));
        if (null === $attribute) {
            return;
        }

        if ($attribute->get('isunique')) {
            Message::addInfo(
                $this->translator->trans(
                    'tl_metamodel_dcasetting.mandatory_for_unique_attr',
                    [],
                    'contao_tl_metamodel_dcasetting'
                )
            );

            $extra = $event->getProperty()->getExtra();

            $extra['disabled'] = true;

            $event->getProperty()->setExtra($extra);

            $model->setProperty('mandatory', true);
        }
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Render\Setting;

use Contao\StringUtil;

/**
 * Base implementation for render settings.
 */
class Simple implements ISimple
{
    /**
     * The base information for this render settings object.
     *
     * @var array
     */
    protected $arrBase = array();

    /**
     * The parenting instance.
     *
     * @var ICollection
     */
    protected $parent;

    /**
     * Create a new instance.
     *
     * @param array $arrInformation The array that holds all base information for the new instance.
     */
    public function __construct($arrInformation = array())
    {
        foreach ($arrInformation as $strKey => $varValue) {
            $this->set($strKey, StringUtil::deserialize($varValue));
        }
    }

    /**
     * Set the parenting render setting.
     *
     * @param ICollection $parent The parenting instance.
     *
     * @return ISimple
     */
    public function setParent($parent)
    {
        $this->parent = $parent;

        return $this;
    }

    /**
     * Retrieve the parenting render setting.
     *
     * @return ICollection
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * Retrieve a setting from the settings instance.
     *
     * @param string $strName The name of the setting to retrieve.
     *
     * @return mixed|null The value or null if not set.
     */
    public function get($strName)
    {
        return isset($this->arrBase[$strName]) ? $this->arrBase[$strName] : null;
    }

    /**
     * Set a base property in the settings object.
     *
     * @param string $strName    The name of the setting to set.
     *
     * @param mixed  $varSetting The value to use.
     *
     * @return ISimple The setting itself.
     */
    public function set($strName, $varSetting)
    {
        $this->arrBase[$strName] = $varSetting;
        return $this;
    }

    /**
     * Retrieve the names of all keys in this setting.
     *
     * @return string[]
     */
    public function getKeys()
    {
        return array_keys($this->arrBase);
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\LegacyCompat;

use MetaModels\MetaModelsServiceContainer;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * This class is the configurator for the legacy service container.
 *
 * @deprecated Only here as legacy gateway for MetaModels 2.0.
 */
class ServiceContainerInitializer
{
    /**
     * The container.
     *
     * @var ContainerInterface
     */
    private $container;

    /**
     * Create a new instance.
     *
     * @param ContainerInterface $container The container.
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * Configure the legacy service container.
     *
     * @param MetaModelsServiceContainer $serviceContainer The container to configure.
     *
     * @return MetaModelsServiceContainer
     */
    public function configure(MetaModelsServiceContainer $serviceContainer)
    {
        $serviceContainer
            ->setEventDispatcher(function () {
                return $this->container->get('event_dispatcher');
            })
            ->setDatabase(function () {
                return $this->container->get('cca.legacy_dic.contao_database_connection');
            })
            ->setAttributeFactory(function () {
                return $this->container->get('metamodels.attribute_factory');
            })
            ->setFactory(function () {
                return $this->container->get('metamodels.factory');
            })
            ->setFilterFactory(function () {
                return $this->container->get('metamodels.filter_setting_factory');
            })
            ->setRenderSettingFactory(function () {
                return $this->container->get('metamodels.render_setting_factory');
            })
            ->setCache(function () {
                return $this->container->get('metamodels.cache');
            });

        return $serviceContainer;
    }
}