<?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>
 * @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\Render\Setting;

use Contao\StringUtil;
use Doctrine\DBAL\Connection;
use MetaModels\Filter\FilterUrlBuilder;
use MetaModels\Filter\Setting\IFilterSettingFactory;
use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;
use MetaModels\MetaModelsEvents;
use MetaModels\Render\Setting\Events\CreateRenderSettingFactoryEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * This is the filter settings factory interface.
 */
class RenderSettingFactory implements IRenderSettingFactory
{
    /**
     * The event dispatcher.
     *
     * @var IMetaModelsServiceContainer
     *
     * @deprecated The service container will get removed.
     */
    private $serviceContainer;

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

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

    /**
     * The filter setting factory.
     *
     * @var IFilterSettingFactory
     */
    private $filterFactory;

    /**
     * The already created render settings.
     *
     * @var ICollection[]
     */
    private $renderSettings;

    /**
     * The filter URL builder.
     *
     * @var FilterUrlBuilder
     */
    private $filterUrlBuilder;

    /**
     * Create a new instance.
     *
     * @param Connection               $database         The database.
     * @param EventDispatcherInterface $eventDispatcher  The event dispatcher to use.
     * @param IFilterSettingFactory    $filterFactory    The filter setting factory.
     * @param FilterUrlBuilder         $filterUrlBuilder The filter URL builder.
     */
    public function __construct(
        Connection $database,
        EventDispatcherInterface $eventDispatcher,
        IFilterSettingFactory $filterFactory,
        FilterUrlBuilder $filterUrlBuilder
    ) {
        $this->eventDispatcher  = $eventDispatcher;
        $this->database         = $database;
        $this->filterFactory    = $filterFactory;
        $this->filterUrlBuilder = $filterUrlBuilder;
    }

    /**
     * Set the service container.
     *
     * @param IMetaModelsServiceContainer $serviceContainer  The service container to use.
     * @param bool                        $deprecationNotice Internal flag to disable deprecation notice.
     *
     * @return RenderSettingFactory
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function setServiceContainer(IMetaModelsServiceContainer $serviceContainer, $deprecationNotice = true)
    {
        if ($deprecationNotice) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                '"' .__METHOD__ . '" is deprecated and will get removed.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
        }
        $this->serviceContainer = $serviceContainer;

        if ($this->eventDispatcher->hasListeners(MetaModelsEvents::RENDER_SETTING_FACTORY_CREATE)) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Event "' .
                MetaModelsEvents::RENDER_SETTING_FACTORY_CREATE .
                '" is deprecated - register your factories via the service container.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd

            $this->serviceContainer->getEventDispatcher()->dispatch(
                MetaModelsEvents::RENDER_SETTING_FACTORY_CREATE,
                new CreateRenderSettingFactoryEvent($this)
            );
        }

        return $this;
    }

    /**
     * Retrieve the service container.
     *
     * @return IMetaModelsServiceContainer
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getServiceContainer()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated - use the services from the service container.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd
        return $this->serviceContainer;
    }

    /**
     * Collect the attribute settings for the given render setting.
     *
     * @param IMetaModel  $metaModel     The MetaModel instance to retrieve the settings for.
     *
     * @param ICollection $renderSetting The render setting.
     *
     * @return void
     */
    public function collectAttributeSettings(IMetaModel $metaModel, $renderSetting)
    {
        $attributeRows = $this
            ->database
            ->createQueryBuilder()
            ->select('*')
            ->from('tl_metamodel_rendersetting')
            ->where('pid=:pid')
            ->andWhere('enabled=1')
            ->orderBy('sorting')
            ->setParameter('pid', $renderSetting->get('id'))
            ->execute();

        foreach ($attributeRows->fetchAll(\PDO::FETCH_ASSOC) as $attributeRow) {
            $attribute = $metaModel->getAttributeById($attributeRow['attr_id']);
            if (!$attribute) {
                continue;
            }

            $attributeSetting = $renderSetting->getSetting($attribute->getColName());
            if (!$attributeSetting) {
                $attributeSetting = $attribute->getDefaultRenderSettings();
            }

            foreach ($attributeRow as $strKey => $varValue) {
                if ($varValue) {
                    $attributeSetting->set($strKey, StringUtil::deserialize($varValue));
                }
            }
            $renderSetting->setSetting($attribute->getColName(), $attributeSetting);
        }
    }

    /**
     * Create a ICollection instance from the id.
     *
     * @param IMetaModel $metaModel The MetaModel for which to retrieve the render setting.
     *
     * @param string     $settingId The id of the ICollection.
     *
     * @return ICollection The instance or null if not found.
     */
    protected function internalCreateRenderSetting(IMetaModel $metaModel, $settingId)
    {
        $row = $this
            ->database
            ->createQueryBuilder()
            ->select('*')
            ->from('tl_metamodel_rendersettings')
            ->where('pid=:pid')
            ->andWhere('id=:id')
            ->setParameter('pid', $metaModel->get('id'))
            ->setParameter('id', $settingId ?: 0)
            ->setMaxResults(1)
            ->execute()
            ->fetch(\PDO::FETCH_ASSOC);

        if (!$row) {
            $row = [];
        }

        $renderSetting = new Collection(
            $metaModel,
            $row,
            $this->eventDispatcher,
            $this->filterFactory,
            $this->filterUrlBuilder
        );

        if ($renderSetting->get('id')) {
            $this->collectAttributeSettings($metaModel, $renderSetting);
        }

        return $renderSetting;
    }

    /**
     * {@inheritdoc}
     */
    public function createCollection(IMetaModel $metaModel, $settingId = '')
    {
        $tableName = $metaModel->getTableName();
        if (!isset($this->renderSettings[$tableName])) {
            $this->renderSettings[$tableName] = array();
        }

        if (!isset($this->renderSettings[$tableName][$settingId])) {
            $this->renderSettings[$tableName][$settingId] = $this->internalCreateRenderSetting($metaModel, $settingId);
        }

        return $this->renderSettings[$tableName][$settingId];
    }
}


<?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\RenderSetting;

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractEnvironmentAwareEvent;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractModelAwareEvent;

/**
 * This provides a way to abstain from handling an event.
 */
abstract class AbstractAbstainingListener
{
    /**
     * The scope determinator.
     *
     * @var RequestScopeDeterminator
     */
    private $scopeDeterminator;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator)
    {
        $this->scopeDeterminator = $scopeDeterminator;
    }

    /**
     * Test if the event is for the correct table and in backend scope.
     *
     * @param AbstractEnvironmentAwareEvent $event The event to test.
     *
     * @return bool
     */
    protected function wantToHandle(AbstractEnvironmentAwareEvent $event)
    {
        if (!$this->scopeDeterminator->currentScopeIsBackend()) {
            return false;
        }

        $environment = $event->getEnvironment();
        if ('tl_metamodel_rendersetting' !== $environment->getDataDefinition()->getName()) {
            return false;
        }

        if ($event instanceof AbstractModelAwareEvent) {
            if ($event->getEnvironment()->getDataDefinition()->getName() !== $event->getModel()->getProviderName()) {
                return false;
            }
        }

        return 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>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\DcGeneral;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyValueCondition;
use MetaModels\IMetaModel;

/**
 * This builds a property condition.
 */
class PropertyValueConditionFactory extends AbstractRestrictedAttributeConditionFactory
{
    /**
     * {@inheritDoc}
     */
    public function buildCondition(array $configuration, IMetaModel $metaModel)
    {
        return new PropertyValueCondition(
            $this->attributeIdToName($metaModel, $configuration['attr_id']),
            $configuration['value']
        );
    }
}


<?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>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\Contao\Hooks;

use Contao\DataContainer;
use MetaModels\ViewCombination\InputScreenInformationBuilder;
use MetaModels\ViewCombination\ViewCombinationBuilder;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * This is called as a HOOK from tl_user_group.
 */
class FixupUserGroupModules
{
    /**
     * The view combination builder.
     *
     * @var ViewCombinationBuilder
     */
    private $combinationBuilder;

    /**
     * The input screen information builder.
     *
     * @var InputScreenInformationBuilder
     */
    private $inputScreens;

    /**
     * The request stack.
     *
     * @var RequestStack
     */
    protected $requestStack;

    /**
     * Create a new instance.
     *
     * @param ViewCombinationBuilder        $combinationBuilder The view combination builder.
     * @param InputScreenInformationBuilder $inputScreens       The input screen information builder.
     * @param RequestStack                  $requestStack       The request stack.
     */
    public function __construct(
        ViewCombinationBuilder $combinationBuilder,
        InputScreenInformationBuilder $inputScreens,
        RequestStack $requestStack
    ) {
        $this->combinationBuilder = $combinationBuilder;
        $this->inputScreens       = $inputScreens;
        $this->requestStack       = $requestStack;
    }

    /**
     * Fix up the modules in the backend.
     *
     * @param DataContainer $dataContainer The current data container.
     *
     * @return array
     *
     * @throws \RuntimeException When the "parenting" class can not be found.
     */
    public function fixupModules(DataContainer $dataContainer)
    {
        if (!class_exists('tl_user_group', false)) {
            throw new \RuntimeException('data container is not loaded!');
        }

        $original = new \tl_user_group();
        $modules  = $original->getModules();

        // 1. remove all MetaModels
        foreach (array_keys($modules) as $group) {
            foreach ($modules[$group] as $key => $module) {
                if (strpos($module, 'metamodel_') === 0) {
                    unset($modules[$group][$key]);
                }
            }
            // Otherwise we end up with an associative array.
            $modules[$group] = array_values($modules[$group]);
        }

        // 2. Add our "custom" modules and remove the main module.
        $modules['metamodels'][] = 'support_metamodels';
        if (false !== $index = array_search('metamodels', $modules['metamodels'], true)) {
            unset($modules['metamodels'][$index]);
            $modules['metamodels'] = array_values($modules['metamodels']);
        }

        // 3. Add back all MetaModels for the current group.
        $combinations = $this->combinationBuilder->getCombinationsForUser([$dataContainer->activeRecord->id], 'be');

        $screenIds = array_map(function ($combination) {
            return $combination['dca_id'];
        }, $combinations['byName']);

        $screens = $this->inputScreens->fetchInputScreens($screenIds);

        $locale = $this->requestStack->getCurrentRequest()->getLocale();
        foreach ($screens as $metaModel => $screen) {
            if ('standalone' === $screen['meta']['rendertype']) {
                $modules[$screen['meta']['backendsection']][] = 'metamodel_' . $metaModel;
                $this->buildLanguageString('metamodel_' . $metaModel, $screen, $locale);
            }
        }

        return $modules;
    }

    /**
     * Build the language string for the passed backend module.
     *
     * @param string $name   The module name.
     * @param array  $screen The input screen information.
     * @param string $locale The locale.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function buildLanguageString($name, $screen, $locale)
    {
        if (isset($screen['label'][$locale])) {
            $GLOBALS['TL_LANG']['MOD'][$name] = $screen['label'][$locale];
            return;
        }

        $GLOBALS['TL_LANG']['MOD'][$name] = $screen['label'][''];
    }
}


<?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     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\DcGeneral\DataDefinition\Palette\Condition\Property;

use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\Data\PropertyValueBag;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\LegendInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\PropertyInterface;
use MetaModels\Filter\Setting\IFilterSettingFactory;
use MetaModels\IMetaModel;

/**
 * Class FilterSettingTypeSubPaletteCondition
 */
final class FilterSettingTypeSubPaletteCondition implements PropertyConditionInterface
{
    /**
     * The filter setting factory.
     *
     * @var IFilterSettingFactory
     */
    private $filterFactory;

    /**
     * The filter type name.
     *
     * @var string
     */
    private $filterType;

    /**
     * Create a new instance.
     *
     * @param IFilterSettingFactory $filterFactory The filter setting factory.
     * @param string                $filterType    The filter type name.
     */
    public function __construct(IFilterSettingFactory $filterFactory, $filterType)
    {
        $this->filterFactory = $filterFactory;
        $this->filterType    = $filterType;
    }

    /**
     * {@inheritdoc}
     */
    public function match(
        ModelInterface $model = null,
        PropertyValueBag $input = null,
        PropertyInterface $property = null,
        LegendInterface $legend = null
    ) {
        if (!$model || !$model->getProperty('attr_id')) {
            return false;
        }

        $metaModel = $this->getMetaModel($model);
        $attribute = $metaModel->getAttributeById($model->getProperty('attr_id'));

        if (!$attribute) {
            return false;
        }

        return $attribute->get('type') === $this->filterType;
    }

    /**
     * {@inheritdoc}
     */
    public function __clone()
    {
    }

    /**
     * Retrieve the MetaModel attached to the model filter setting.
     *
     * @param ModelInterface $model The model for which to retrieve the MetaModel.
     *
     * @return IMetaModel
     */
    private function getMetaModel(ModelInterface $model)
    {
        $filterSetting = $this->filterFactory->createCollection($model->getProperty('fid'));

        return $filterSetting->getMetaModel();
    }
}


<?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\RenderSetting;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\ConditionChainInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Palette\PaletteConditionChain;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\PaletteInterface;
use ContaoCommunityAlliance\DcGeneral\Factory\Event\BuildDataDefinitionEvent;
use Doctrine\DBAL\Connection;
use MetaModels\CoreBundle\EventListener\DcGeneral\Table\AbstractPaletteRestrictionListener;
use MetaModels\DcGeneral\DataDefinition\Palette\Condition\Palette\RenderSettingAttributeIs as PaletteCondition;
use MetaModels\DcGeneral\DataDefinition\Palette\Condition\Property\AttributeByIdIsOfType;

/**
 * This builds the palette conditions as specified by the configuration.
 */
class PaletteRestrictionListener extends AbstractPaletteRestrictionListener
{
    /**
     * Database connection.
     *
     * @var Connection
     */
    private $connection;

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

    /**
     * Build the data definition palettes.
     *
     * @param BuildDataDefinitionEvent $event The event.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function handle(BuildDataDefinitionEvent $event)
    {
        if (($event->getContainer()->getName() !== 'tl_metamodel_rendersetting')) {
            return;
        }

        $palettes = $event->getContainer()->getPalettesDefinition();

        foreach ($palettes->getPalettes() as $palette) {
            if ($palette->getName() !== 'default') {
                $paletteCondition = $palette->getCondition();
                if (!($paletteCondition instanceof ConditionChainInterface)
                    || ($paletteCondition->getConjunction() !== PaletteConditionChain::OR_CONJUNCTION)
                ) {
                    $paletteCondition = new PaletteConditionChain(
                        $paletteCondition ? array($paletteCondition) : array(),
                        PaletteConditionChain::OR_CONJUNCTION
                    );
                    $palette->setCondition($paletteCondition);
                }
                $paletteCondition->addCondition(new PaletteCondition($palette->getName(), 1, $this->connection));
            }

            $this->buildMetaPaletteConditions(
                $palette,
                (array) $GLOBALS['TL_DCA']['tl_metamodel_rendersetting']['metapalettes']
            );
        }
    }

    /**
     * Apply conditions for meta palettes of the certain render setting types.
     *
     * @param PaletteInterface $palette      The palette.
     * @param array            $metaPalettes The meta palette information.
     *
     * @return void
     */
    private function buildMetaPaletteConditions($palette, $metaPalettes)
    {
        foreach ($metaPalettes as $typeName => $paletteInfo) {
            if ('default' === $typeName) {
                continue;
            }

            if (preg_match('#^(\w+) extends (\w+)$#', $typeName, $matches)) {
                $typeName = $matches[1];
            }

            foreach ($paletteInfo as $legendName => $properties) {
                foreach ($properties as $propertyName) {
                    $condition = new AttributeByIdIsOfType($typeName, $this->connection, 'attr_id');
                    $legend    = $this->getLegend($legendName, $palette);
                    $property  = $this->getProperty($propertyName, $legend);
                    $this->addCondition($property, $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>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\DcGeneral;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionInterface;
use MetaModels\CoreBundle\DependencyInjection\IdProvidingServiceLocator;
use MetaModels\IMetaModel;

/**
 * This factory takes care of building property conditions.
 */
class PropertyConditionFactory
{
    /**
     * The factories.
     *
     * @var IdProvidingServiceLocator
     */
    private $factories;

    /**
     * The fallback factory.
     *
     * @var FallbackPropertyConditionFactory
     */
    private $fallbackFactory;

    /**
     * Create a new instance.
     *
     * @param IdProvidingServiceLocator        $factories       The factories.
     * @param FallbackPropertyConditionFactory $fallbackFactory The fallback factory.
     */
    public function __construct(IdProvidingServiceLocator $factories, FallbackPropertyConditionFactory $fallbackFactory)
    {
        $this->factories       = $factories;
        $this->fallbackFactory = $fallbackFactory;
    }

    /**
     * Obtain the list of registered type names.
     *
     * @return array
     */
    public function getTypeNames()
    {
        $names = $this->factories->ids();
        if ([] !== $fallback = $this->fallbackFactory->getIds()) {
            $names = array_unique(array_merge($fallback, $names));
        }

        return $names;
    }

    /**
     * Test if the passed type supports nesting.
     *
     * @param string $conditionType The type name.
     *
     * @return bool
     */
    public function supportsNesting($conditionType)
    {
        $factory = $this->factories->has($conditionType) ? $this->getFactory($conditionType) : null;

        return ($factory instanceof NestablePropertyConditionFactoryInterface)
            || (bool) $this->fallbackFactory->supportsNesting($conditionType);
    }

    /**
     * Get the amount of children this type supports - for unlimited, returns -1.
     *
     * @param string $conditionType The type name.
     *
     * @return int
     */
    public function maxChildren($conditionType)
    {
        $factory = $this->factories->has($conditionType) ? $this->getFactory($conditionType) : null;
        if (!$factory instanceof NestablePropertyConditionFactoryInterface) {
            if (null !== $value = $this->fallbackFactory->maxChildren($conditionType)) {
                return $value;
            }
            return 0;
        }

        return $factory->maxChildren();
    }

    /**
     * Test if an attribute type is supported for the passed condition type.
     *
     * @param string $conditionType The condition type.
     * @param string $attribute     The attribute type.
     *
     * @return bool
     */
    public function supportsAttribute($conditionType, $attribute)
    {
        $factory = $this->factories->has($conditionType) ? $this->getFactory($conditionType) : null;

        return (($factory instanceof AttributeAwarePropertyConditionFactoryInterface)
            && $factory->supportsAttribute($attribute))
            || (bool) $this->fallbackFactory->supportsAttribute($conditionType, $attribute);
    }

    /**
     * Create a condition from the passed configuration.
     *
     * @param array      $configuration The configuration.
     * @param IMetaModel $metaModel     The MetaModel instance.
     *
     * @return PropertyConditionInterface
     *
     * @throws \InvalidArgumentException When the type is unknown.
     */
    public function createCondition(array $configuration, IMetaModel $metaModel)
    {
        if (!isset($configuration['type'])) {
            throw new \InvalidArgumentException('No type given in configuration');
        }

        if (!$this->factories->has($typeName = $configuration['type'])) {
            if ($result = $this->fallbackFactory->createCondition($configuration, $metaModel)) {
                return $result;
            }

            throw new \InvalidArgumentException('Unknown type: ' . $typeName);
        }

        return $this->getFactory($typeName)->buildCondition($configuration, $metaModel);
    }

    /**
     * Fetch a factory for the passed type.
     *
     * @param string $conditionType The type name.
     *
     * @return PropertyConditionFactoryInterface
     */
    private function getFactory($conditionType)
    {
        return $this->factories->get($conditionType);
    }
}


<?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     Christoph Wiechert <christoph.wiechert@4wardmedia.de>
 * @author     Andreas Nölke <zero@brothers-project.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @author     David Maack <david.maack@arcor.de>
 * @author     Stefan Heimes <stefan_heimes@hotmail.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\Widgets;

use Contao\Backend;
use Contao\BackendTemplate;
use Contao\BackendUser;
use Contao\Controller;
use Contao\Environment;
use Contao\Input;
use RuntimeException;

/**
 * This class handles the DCA style picker.
 */
class PickerWidget
{
    /**
     * Initialize the controller.
     *
     * The workflow is:
     * 1. Import user.
     * 2. Call parent constructor
     * 3. Authenticate user
     * 4. Load language files
     * DO NOT CHANGE THIS ORDER!
     */
    public function __construct()
    {
        BackendUser::getInstance()->authenticate();
        Controller::setStaticUrls();
        Controller::loadLanguageFile('default');
        Controller::loadLanguageFile('modules');
    }

    /**
     * Run controller and parse the login template.
     *
     * @return void
     *
     * @throws RuntimeException When the fieldname is invalid.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function run()
    {
        $template           = new BackendTemplate('be_dcastylepicker');
        $template->main     = '';
        $template->headline = $GLOBALS['TL_LANG']['MSC']['metamodelspicker'];

        $inputName = Input::get('inputName');
        if (!preg_match('~^[a-z\-_0-9]+$~i', $inputName)) {
            throw new RuntimeException('Field-Parameter ERROR!');
        }

        $template->field = $inputName;
        $template->items = $GLOBALS[Input::get('item')];

        if (!strlen($template->headline)) {
            $template->headline = $GLOBALS['TL_CONFIG']['websiteTitle'];
        }

        $template->theme          = Backend::getTheme();
        $template->base           = Environment::get('base');
        $template->language       = $GLOBALS['TL_LANGUAGE'];
        $template->title          = $GLOBALS['TL_CONFIG']['websiteTitle'];
        $template->charset        = $GLOBALS['TL_CONFIG']['characterSet'];
        $template->pageOffset     = Input::cookie('BE_PAGE_OFFSET');
        $template->error          = (Input::get('act') == 'error') ? $GLOBALS['TL_LANG']['ERR']['general'] : '';
        $template->skipNavigation = $GLOBALS['TL_LANG']['MSC']['skipNavigation'];
        $template->request        = ampersand(Environment::get('request'));
        $template->top            = $GLOBALS['TL_LANG']['MSC']['backToTop'];
        $template->be27           = !$GLOBALS['TL_CONFIG']['oldBeTheme'];
        $template->expandNode     = $GLOBALS['TL_LANG']['MSC']['expandNode'];
        $template->collapseNode   = $GLOBALS['TL_LANG']['MSC']['collapseNode'];
        $template->strField       = Input::get('fld');

        $template->output();
    }
}


<?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\RenderSetting;

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use Doctrine\DBAL\Connection;
use MetaModels\BackendIntegration\TemplateList;
use MetaModels\IFactory;

/**
 * This handles the providing of available templates.
 */
class TemplateOptionListener extends AbstractListener
{
    /**
     * The template list provider.
     *
     * @var TemplateList
     */
    private $templateList;

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

    /**
     * Retrieve the options for the attributes.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     */
    public function handle(GetPropertyOptionsEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getPropertyName() !== 'template')) {
            return;
        }

        $model     = $event->getModel();
        $metaModel = $this->getMetaModelFromModel($model);
        $attribute = $metaModel->getAttributeById($model->getProperty('attr_id'));

        if (!$attribute) {
            return;
        }

        $event->setOptions($this->templateList->getTemplatesForBase('mm_attr_' . $attribute->get('type')));
    }
}


<?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     Ingolf Steinhardt <info@e-spin.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\Setting;

use MetaModels\Attribute\IAttribute;
use MetaModels\FrontendIntegration\FrontendFilterOptions;
use MetaModels\IItem;
use MetaModels\Filter\IFilter;
use MetaModels\Filter\Rules\StaticIdList as FilterRuleStaticIdList;
use MetaModels\Filter\Rules\SearchAttribute as FilterRuleSimpleLookup;
use MetaModels\Render\Setting\ICollection as IRenderSettings;

/**
 * Filter setting implementation performing a search for a value on a
 * configured attribute.
 */
class SimpleLookup extends Simple
{
    /**
     * Retrieve the filter parameter name to react on.
     *
     * @return string|null
     */
    protected function getParamName()
    {
        if ($this->get('urlparam')) {
            return $this->get('urlparam');
        }

        $objAttribute = $this->getFilteredAttribute();
        if ($objAttribute) {
            return $objAttribute->getColName();
        }

        return null;
    }

    /**
     * Return the label to use.
     *
     * @return string|null
     */
    protected function getLabel()
    {
        if (null === ($attribute = $this->getFilteredAttribute())) {
            return null;
        }
        if ($label = $this->get('label')) {
            return $label;
        }

        return $attribute->getName();
    }

    /**
     * Determine if this filter setting shall return all matches if no url param has been specified.
     *
     * @return bool true if all matches shall be returned, false otherwise.
     */
    public function allowEmpty()
    {
        return (bool) $this->get('allow_empty');
    }

    /**
     * Internal helper function for descendant classes to retrieve the options.
     *
     * @param IAttribute    $objAttribute The attribute to search.
     *
     * @param string[]|null $arrIds       The Id list of items for which to retrieve the options.
     *
     * @param array         $arrCount     If non null, the amount of matches will get returned.
     *
     * @return array
     */
    protected function getParameterFilterOptions($objAttribute, $arrIds, &$arrCount = null)
    {
        $arrOptions = $objAttribute->getFilterOptions(
            $this->get('onlypossible') ? $arrIds : null,
            (bool) $this->get('onlyused'),
            $arrCount
        );

        // Remove empty values.
        foreach ($arrOptions as $mixOptionKey => $mixOptions) {
            // Remove html/php tags.
            $mixOptions = strip_tags($mixOptions);
            $mixOptions = trim($mixOptions);

            if ($mixOptions === '' || $mixOptions === null) {
                unset($arrOptions[$mixOptionKey]);
            }
        }

        return $arrOptions;
    }

    /**
     * Determine if this filter setting shall be available for frontend filter widget generating.
     *
     * @return bool true if available, false otherwise.
     */
    public function enableFEFilterWidget()
    {
        return (bool) $this->get('fe_widget');
    }

    /**
     * {@inheritdoc}
     */
    public function prepareRules(IFilter $objFilter, $arrFilterUrl)
    {
        $objMetaModel = $this->getMetaModel();
        $objAttribute = $this->getFilteredAttribute();
        $strParam     = $this->getParamName();

        if ($objAttribute && $strParam) {
            if ($arrFilterValue = $this->determineFilterValue($arrFilterUrl, $strParam)) {
                if ($objMetaModel->isTranslated() && $this->get('all_langs')) {
                    $arrLanguages = $objMetaModel->getAvailableLanguages();
                } else {
                    $arrLanguages = array($objMetaModel->getActiveLanguage());
                }
                $objFilterRule = new FilterRuleSimpleLookup($objAttribute, $arrFilterValue, $arrLanguages);
                $objFilter->addFilterRule($objFilterRule);
                return;
            }

            // We found an attribute but no match in URL. So ignore this filter setting if allow_empty is set.
            if ($this->allowEmpty()) {
                $objFilter->addFilterRule(new FilterRuleStaticIdList(null));
                return;
            }
        }
        // Either no attribute found or no match in url, do not return anything.
        $objFilter->addFilterRule(new FilterRuleStaticIdList(array()));
    }

    /**
     * {@inheritdoc}
     */
    public function generateFilterUrlFrom(IItem $objItem, IRenderSettings $objRenderSetting)
    {
        if ($attribute = $this->getFilteredAttribute()) {
            return [$this->getParamName() => $attribute->getFilterUrlValue($objItem->get($attribute->getColName()))];
        }

        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getParameters()
    {
        return ($strParamName = $this->getParamName()) ? array($strParamName) : array();
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getParameterDCA()
    {
        // If defined as static, return nothing as not to be manipulated via editors.
        if (!$this->get('predef_param')) {
            return array();
        }

        $objAttribute = $this->getFilteredAttribute();
        $arrOptions   = $objAttribute->getFilterOptions(null, (bool) $this->get('onlyused'));

        return array(
            $this->getParamName() => array
            (
                'label'   => array(
                    sprintf(
                        $GLOBALS['TL_LANG']['MSC']['metamodel_filtersettings_parameter']['simplelookup'][0],
                        $objAttribute->getName()
                    ),
                    sprintf(
                        $GLOBALS['TL_LANG']['MSC']['metamodel_filtersettings_parameter']['simplelookup'][1],
                        $objAttribute->getName()
                    )
                ),
                'inputType'    => 'select',
                'options' => $arrOptions,
                'eval' => array(
                    'includeBlankOption' => true,
                    'style' => 'min-width:450px;width:450px;margin-bottom:16px;margin-right:10px;'
                )
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterFilterNames()
    {
        if (($label = $this->getLabel()) && ($paramName = $this->getParamName())) {
            return array(
                $paramName => $label
            );
        }

        return array();
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getParameterFilterWidgets(
        $arrIds,
        $arrFilterUrl,
        $arrJumpTo,
        FrontendFilterOptions $objFrontendFilterOptions
    ) {
        // If defined as static, return nothing as not to be manipulated via editors.
        if (!$this->enableFEFilterWidget()) {
            return array();
        }

        if (!($attribute = $this->getFilteredAttribute())) {
            return array();
        }

        $GLOBALS['MM_FILTER_PARAMS'][] = $this->getParamName();

        $arrCount  = array();
        $arrWidget = array(
            'label'     => array(
                $this->getLabel(),
                'GET: ' . $this->getParamName()
            ),
            'inputType' => 'select',
            'options'   => $this->getParameterFilterOptions($attribute, $arrIds, $arrCount),
            'count'     => $arrCount,
            'showCount' => $objFrontendFilterOptions->isShowCountValues(),
            'eval'      => array
            (
                'includeBlankOption' => (
                        $this->get('blankoption') && !$objFrontendFilterOptions->isHideClearFilter()
                        ? true
                        : false
                    ),
                'blankOptionLabel'   => &$GLOBALS['TL_LANG']['metamodels_frontendfilter']['do_not_filter'],
                'colname'            => $attribute->getColname(),
                'urlparam'           => $this->getParamName(),
                'onlyused'           => $this->get('onlyused'),
                'onlypossible'       => $this->get('onlypossible'),
                'template'           => $this->get('template'),
            )
        );

        return array
        (
            $this->getParamName() => $this->prepareFrontendFilterWidget(
                $arrWidget,
                $arrFilterUrl,
                $arrJumpTo,
                $objFrontendFilterOptions
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getReferencedAttributes()
    {
        if ($attribute = $this->getFilteredAttribute()) {
            return array($attribute->getColName());
        }

        return array();
    }

    /**
     * Retrieve the attribute we are filtering on.
     *
     * @return IAttribute|null
     */
    protected function getFilteredAttribute()
    {
        if (!($attributeId = $this->get('attr_id'))) {
            return null;
        }

        if ($attribute = $this->getMetaModel()->getAttributeById($attributeId)) {
            return $attribute;
        }

        return null;
    }

    /**
     * Determine the filter value from the passed values.
     *
     * @param array  $filterValues The filter values.
     * @param string $valueName    The parameter name to obtain.
     *
     * @return mixed|null
     */
    private function determineFilterValue($filterValues, $valueName)
    {
        if (!isset($filterValues[$valueName]) && $this->get('defaultid')) {
            return $this->get('defaultid');
        }

        return $filterValues[$valueName];
    }
}


<?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\Attribute;

use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ModelToLabelEvent;

/**
 * This renders attribute information in the backend listing.
 */
class AttributeRendererListener extends BaseListener
{
    /**
     * Draw the attribute in the backend listing.
     *
     * @param ModelToLabelEvent $event The event.
     *
     * @return void
     */
    public function modelToLabel(ModelToLabelEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $model     = $event->getModel();
        $type      = $model->getProperty('type');
        $image     = 'attributeFactory->getIconForType($type) . '" />';
        $metaModel = $this->getMetaModelByModelPid($model);

        $attribute = $this->attributeFactory->createAttribute($model->getPropertiesAsArray(), $metaModel);

        if (!$attribute) {
            $translator = $event
                ->getEnvironment()
                ->getTranslator();

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

                        %s

'
                )
                ->setArgs(
                    array
                    (
                        $translator->translate('error_unknown_attribute.0', 'tl_metamodel_attribute'),
                        $type,
                        $translator->translate('error_unknown_attribute.1', 'tl_metamodel_attribute', array($type)),
                    )
                );
            return;
        }

        $colName        = $attribute->getColName();
        $name           = $attribute->getName();
        $arrDescription = StringUtil::deserialize($attribute->get('description'));
        if (is_array($arrDescription)) {
            $description = $arrDescription[$attribute->getMetaModel()->getActiveLanguage()];
            if (!$description) {
                $description = $arrDescription[$attribute->getMetaModel()->getFallbackLanguage()];
            }
        } else {
            $description = $arrDescription ?: $attribute->getName();
        }

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

                    %s%s - %s

'
            )
            ->setArgs(array(
                $colName,
                $type,
                $image,
                $name,
                $description
            ));
    }
}


<?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>
 * @author     Andreas Nölke <zero@brothers-project.de>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\FrontendIntegration;

/**
 * Content element clearing the FE-filter.
 *
 * @property \FrontendTemplate $Template
 */
abstract class HybridFilterClearAll extends MetaModelHybrid
{
    /**
     * The name to display in the wildcard.
     *
     * @var string
     */
    protected $wildCardName = '### METAMODEL FILTER ELEMENT CLEAR ALL ###';

    /**
     * Template.
     *
     * @var string
     */
    protected $strTemplate = 'mm_filter_clearall';

    /**
     * The current element type.
     *
     * @var string
     */
    protected $type;

    /**
     * Generate the list.
     *
     * @return string
     */
    public function generate()
    {
        if (TL_MODE == 'BE') {
            return parent::generate();
        }

        return sprintf('[[[metamodelfrontendfilterclearall::%s::%s]]]', $this->type, $this->id);
    }

    /**
     * Generate the module.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function compile()
    {
        $blnActiveParam   = false;
        $arrPage          = $GLOBALS['objPage']->row();
        $arrGetParameters = array();

        foreach (array_keys($_GET) as $mixGetKey) {
            if (in_array($mixGetKey, $GLOBALS['MM_FILTER_PARAMS'])) {
                $blnActiveParam = true;
                continue;
            }

            $arrGetParameters[$mixGetKey] = \Input::get($mixGetKey, false, true);
        }

        // Check if we have filter and if we have active params.
        $this->Template->active      = (
            !is_array($GLOBALS['MM_FILTER_PARAMS'])
            || count($GLOBALS['MM_FILTER_PARAMS']) == 0
        );
        $this->Template->activeParam = $blnActiveParam;

        // Build FE url.
        $this->Template->href = $this->generateFrontendUrl($arrPage, $this->getJumpToUrl($arrGetParameters));
    }

    /**
     * Call the generate() method from parent class.
     *
     * @return string
     */
    public function generateReal()
    {
        // Fallback template.
        if (!empty($this->metamodel_fef_template)) {
            $this->strTemplate = $this->metamodel_fef_template;
        }

        return parent::generate();
    }

    /**
     * Generate an url determined by the given params and configured jumpTo page.
     *
     * @param array $arrParams The URL parameters to use.
     *
     * @return string the generated URL.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function getJumpToUrl($arrParams)
    {
        $strFilterAction = '';
        foreach ($arrParams as $strName => $varParam) {
            // Skip the magic "language" parameter.
            if (($strName == 'language') && $GLOBALS['TL_CONFIG']['addLanguageToUrl']) {
                continue;
            }

            $strValue = $varParam;

            if (is_array($varParam)) {
                $strValue = implode(',', array_filter($varParam));
            }

            if (strlen($strValue)) {
                // Shift auto_item to the front.
                if ($strName == 'auto_item') {
                    $strFilterAction = '/' . $strValue . $strFilterAction;
                    continue;
                }

                $strFilterAction .= sprintf(
                    $GLOBALS['TL_CONFIG']['disableAlias'] ? '&%s=%s' : '/%s/%s',
                    $strName,
                    urlencode($strValue)
                );
            }
        }
        return $strFilterAction;
    }
}


<?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 ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;

/**
 * Generate a breadcrumb for table tl_metamodel_dcasetting_condition.
 */
class BreadcrumbDcaSettingConditionListener extends AbstractBreadcrumbListener
{
    use ConnectionTrait;
    use GetMetaModelTrait;

    /**
     * {@inheritDoc}
     */
    protected function wantToHandle(GetBreadcrumbEvent $event)
    {
        return 'tl_metamodel_dcasetting_condition' === $event->getEnvironment()->getDataDefinition()->getName();
    }

    /**
     * {@inheritDoc}
     */
    protected function getBreadcrumbElements(EnvironmentInterface $environment, BreadcrumbStore $elements)
    {
        if (!$elements->hasId('tl_metamodel_dcasetting')) {
            $elements->setId('tl_metamodel_dcasetting', $this->extractIdFrom($environment, 'pid'));
        }

        parent::getBreadcrumbElements($environment, $elements);

        $builder = UrlBuilder::fromUrl($elements->getUri())
            ->setQueryParameter('do', 'metamodels')
            ->setQueryParameter('table', 'tl_metamodel_dcasetting_condition')
            ->setQueryParameter(
                'pid',
                ModelId::fromValues(
                    'tl_metamodel_dcasetting',
                    $elements->getId('tl_metamodel_dcasetting')
                )->getSerialized()
            )
            ->unsetQueryParameter('act')
            ->unsetQueryParameter('id');

        $elements->push(
            ampersand($builder->getUrl()),
            sprintf(
                $elements->getLabel('tl_metamodel_dcasetting_condition'),
                $this->getConditionAttribute($elements->getId('tl_metamodel_dcasetting'))
            ),
            'bundles/metamodelscore/images/icons/dca_condition.png'
        );
    }

    /**
     * Calculate the name of a sub palette attribute.
     *
     * @param string $settingId The id of the tl_metamodel_dcasetting.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getConditionAttribute($settingId)
    {
        $setting = $this->getRow($settingId, 'tl_metamodel_dcasetting');

        if ($setting->dcatype == 'attribute') {
            $attribute     = (object) $this->getRow($setting->attr_id, 'tl_metamodel_attribute');
            $metaModelName = $this->factory->translateIdToMetaModelName($attribute->pid);
            $attribute     = $this->factory->getMetaModel($metaModelName)->getAttributeById($attribute->id);
            if ($attribute) {
                return $attribute->getName();
            }
        } else {
            $title = StringUtil::deserialize($setting->legendtitle, true);
            return isset($title[$GLOBALS['TL_LANGUAGE']]) ? $title[$GLOBALS['TL_LANGUAGE']] : current($title);
        }

        return 'unknown ' . $setting->dcatype;
    }
}


<?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\DcaSetting;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyValueCondition;
use ContaoCommunityAlliance\DcGeneral\Factory\Event\BuildDataDefinitionEvent;
use Doctrine\DBAL\Connection;
use MetaModels\CoreBundle\EventListener\DcGeneral\Table\AbstractPaletteRestrictionListener;
use MetaModels\DcGeneral\DataDefinition\Palette\Condition\Property\AttributeByIdIsOfType;

/**
 * This builds the palette conditions as specified by the configuration.
 */
class PaletteRestrictionListener extends AbstractPaletteRestrictionListener
{
    /**
     * Database connection.
     *
     * @var Connection
     */
    private $connection;

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

    /**
     * Build the data definition palettes.
     *
     * @param BuildDataDefinitionEvent $event The event.
     *
     * @return void
     */
    public function handle(BuildDataDefinitionEvent $event)
    {
        if (($event->getContainer()->getName() !== 'tl_metamodel_dcasetting')) {
            return;
        }

        $palettes = $event->getContainer()->getPalettesDefinition();
        $legend   = null;

        $subSelectPalettes = $this->getSubSelectPalettes();
        foreach ($palettes->getPalettes() as $palette) {
            $condition = new PropertyValueCondition('dcatype', 'attribute');
            $legend    = $this->getLegend('functions', $palette, $legend);
            $property  = $this->getProperty('readonly', $legend);
            $this->addCondition($property, $condition);
            $legend   = $this->getLegend('title', $palette, $legend);
            $property = $this->getProperty('attr_id', $legend);
            $this->addCondition($property, $condition);

            $condition = new PropertyValueCondition('dcatype', 'legend');
            $legend    = $this->getLegend('title', $palette);
            $property  = $this->getProperty('legendtitle', $legend);

            $this->addCondition($property, $condition);
            $property = $this->getProperty('legendhide', $legend);
            $this->addCondition($property, $condition);

            foreach ($subSelectPalettes as $typeName => $paletteInfo) {
                foreach ($paletteInfo as $legendName => $properties) {
                    foreach ($properties as $propertyName) {
                        $condition = new AttributeByIdIsOfType($typeName, $this->connection, 'attr_id');
                        $legend    = $this->getLegend($legendName, $palette);
                        $property  = $this->getProperty($propertyName, $legend);
                        $this->addCondition($property, $condition);
                    }
                }
            }
        }
    }

    /**
     * Obtain the sub select palette settings.
     *
     * @return array
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getSubSelectPalettes()
    {
        if (!isset($GLOBALS['TL_DCA']['tl_metamodel_dcasetting']['metasubselectpalettes']['attr_id'])) {
            return [];
        }

        return (array) $GLOBALS['TL_DCA']['tl_metamodel_dcasetting']['metasubselectpalettes']['attr_id'];
    }
}


<?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 ContaoCommunityAlliance\DcGeneral\Clipboard\Filter;
use ContaoCommunityAlliance\DcGeneral\Clipboard\ItemInterface;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPasteButtonEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use MetaModels\Filter\Setting\IFilterSettingFactory;

/**
 * This class takes care of enabling and disabling of the paste button.
 */
class PasteButtonListener
{
    /**
     * The filter setting factory.
     *
     * @var IFilterSettingFactory
     */
    private $filterFactory;

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

    /**
     * Generate the paste button.
     *
     * @param GetPasteButtonEvent $event The event.
     *
     * @return void
     */
    public function handle(GetPasteButtonEvent $event)
    {
        $model = $event->getModel();
        if (('tl_metamodel_filtersetting' !== $model->getProviderName())) {
            return;
        }

        $environment = $event->getEnvironment();
        $model       = $event->getModel();
        $clipboard   = $environment->getClipboard();

        $filter = Filter::create()->andModelIs(ModelId::fromModel($model))->andActionIs(ItemInterface::CUT);
        // Disable all buttons if there is a circular reference.
        if ($event->isCircularReference() || !$clipboard->isEmpty($filter)) {
            $event
                ->setPasteAfterDisabled(true)
                ->setPasteIntoDisabled(true);

            return;
        }
        $factory = $this->filterFactory->getTypeFactory($model->getProperty('type'));

        // If setting does not support children, omit them.
        if ($model->getId() && !($factory && $factory->isNestedType())) {
            $event->setPasteIntoDisabled(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     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\DcaCombine;

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface;
use ContaoCommunityAlliance\DcGeneral\Data\ModelManipulator;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\PropertiesDefinitionInterface;

/**
 * The class fix the type safety values.
 */
class FixTypeSafetyListener
{
    /**
     * Handle event to update the empty type safety values for DCA combinations.
     *
     * @param EncodePropertyValueFromWidgetEvent $event The event.
     *
     * @return void
     */
    public function handle(EncodePropertyValueFromWidgetEvent $event)
    {
        if (('tl_metamodel_dca_combine' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('rows' !== $event->getProperty())) {
            return;
        }

        $environment  = $event->getEnvironment();
        $dataProvider = $environment->getDataProvider();
        $properties   = $environment->getDataDefinition()->getPropertiesDefinition();

        $values = (array) $event->getValue();
        foreach ($values as $row => $current) {
            $values[$row] = $this->updateValues($current, $properties, $dataProvider);
        }

        $event->setValue($values);
    }

    /**
     * Update type safety values.
     *
     * @param array                         $values       The values for update.
     * @param PropertiesDefinitionInterface $properties   The properties.
     * @param DataProviderInterface         $dataProvider The data provider.
     *
     * @return array
     */
    private function updateValues(
        array &$values,
        PropertiesDefinitionInterface $properties,
        DataProviderInterface $dataProvider
    ) {
        foreach ($values as $propertyName => $propertyValue) {
            if (($dataProvider->getIdProperty() === $propertyName)
                || ($dataProvider->getGroupColumnProperty() === $propertyName)
                || ($dataProvider->getSortingColumnProperty() === $propertyName)
                || ($dataProvider->getTimeStampProperty() === $propertyName)
                || !$properties->hasProperty($propertyName)
            ) {
                continue;
            }

            $values[$propertyName] =
                ModelManipulator::sanitizeValue($properties->getProperty($propertyName), $propertyValue);
        }

        return $values;
    }
}


<?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>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\DcGeneral;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\BooleanCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\NotCondition;
use MetaModels\IMetaModel;

/**
 * This builds not conditions.
 */
class NotConditionFactory implements NestablePropertyConditionFactoryInterface
{
    /**
     * The factory for building child conditions.
     *
     * @var PropertyConditionFactory
     */
    private $factory;

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

    /**
     * {@inheritDoc}
     *
     * @throws \InvalidArgumentException When there are more than one children.
     */
    public function buildCondition(array $configuration, IMetaModel $metaModel)
    {
        // No children, then return "true".
        if (empty($configuration['children'])) {
            return new NotCondition(new BooleanCondition(false));
        }
        if (1 < $count = count($configuration['children'])) {
            throw new \InvalidArgumentException('NOT conditions may only contain one child, ' . $count . ' given.');
        }

        return new NotCondition($this->factory->createCondition($configuration['children'][0], $metaModel));
    }

    /**
     * Return 1.
     *
     * @return int
     */
    public function maxChildren()
    {
        return 1;
    }
}


<?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     David Molineus <david.molineus@netzmacht.de>
 * @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\MetaModel;

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetOperationButtonEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\ViewHelpers;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;

/**
 * Class CutButtonListener handles the cut button for a metamodels item view.
 */
class CutButtonListener
{
    /**
     * Handle the event.
     *
     * @param GetOperationButtonEvent $event The event.
     *
     * @return void
     */
    public function handle(GetOperationButtonEvent $event)
    {
        if (!$event->getEnvironment()->getDataDefinition() instanceof IMetaModelDataDefinition) {
            return;
        }

        $command = $event->getCommand();

        if ($command->getName() === 'cut') {
            $sortingProperty = ViewHelpers::getManualSortingProperty($event->getEnvironment());

            if (!$sortingProperty) {
                $event->setDisabled(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\DcGeneral\DataDefinition\Palette\Condition\Property;

use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\Data\PropertyValueBag;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\LegendInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\PropertyInterface;

/**
 * Condition to check if the current table in the model is a metamodel or not.
 */
class ConditionTableNameIsMetaModel implements PropertyConditionInterface
{
    /**
     * The name of the property in the passed model which contains the table name.
     *
     * @var string
     */
    protected $tablePropertyName;

    /**
     * The expected property value.
     *
     * @var mixed
     */
    protected $desiredValue;

    /**
     * Create a new instance.
     *
     * @param string $tableProperty The name of the property in the passed model which contains the table name.
     *
     * @param string $desiredValue  The desired value, true if the table shall be a MetaModel, false otherwise.
     */
    public function __construct($tableProperty, $desiredValue)
    {
        $this->tablePropertyName = $tableProperty;
        $this->desiredValue      = $desiredValue;
    }

    /**
     * Set the desired value.
     *
     * @param string $desiredValue The desired value.
     *
     * @return ConditionTableNameIsMetaModel
     */
    public function setDesiredValue($desiredValue)
    {
        $this->desiredValue = $desiredValue;

        return $this;
    }

    /**
     * Retrieve the desired value.
     *
     * @return mixed
     */
    public function getDesiredValue()
    {
        return $this->desiredValue;
    }

    /**
     * Set the name of the property in the passed model which contains the table name.
     *
     * @param string $tablePropertyName The name of the property.
     *
     * @return ConditionTableNameIsMetaModel
     */
    public function setTablePropertyName($tablePropertyName)
    {
        $this->tablePropertyName = $tablePropertyName;

        return $this;
    }

    /**
     * Retrieve the name of the property in the passed model which contains the table name.
     *
     * @return string
     */
    public function getTablePropertyName()
    {
        return $this->tablePropertyName;
    }

    /**
     * {@inheritdoc}
     */
    public function match(
        ModelInterface $model = null,
        PropertyValueBag $input = null,
        PropertyInterface $property = null,
        LegendInterface $legend = null
    ) {
        $propertyName = $this->getTablePropertyName();
        if ($input && $input->hasPropertyValue($propertyName)) {
            $value = $input->getPropertyValue($propertyName);
        } elseif ($model) {
            $value = $model->getProperty($propertyName);
        } else {
            return false;
        }

        return $this->desiredValue == (substr($value, 0, 3) === 'mm_');
    }

    /**
     * {@inheritdoc}
     */
    public function __clone()
    {
    }
}


<?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\DcGeneral\Events;

use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;

/**
 * Base class for central event subscriber implementation.
 *
 * @SuppressWarnings(PHPMD.NumberOfChildren) - this is a stupid general purpose class.
 */
class BaseSubscriber
{
    /**
     * The MetaModel service container.
     *
     * @var IMetaModelsServiceContainer
     */
    protected $serviceContainer;

    /**
     * Create a new instance.
     *
     * @param IMetaModelsServiceContainer $serviceContainer The MetaModel service container.
     */
    public function __construct(IMetaModelsServiceContainer $serviceContainer)
    {
        $this->serviceContainer = $serviceContainer;

        $this->registerEventsInDispatcher();
    }

    /**
     * Retrieve the service container.
     *
     * @return IMetaModelsServiceContainer
     */
    protected function getServiceContainer()
    {
        return $this->serviceContainer;
    }

    /**
     * Retrieve the database.
     *
     * @return \Contao\Database
     */
    protected function getDatabase()
    {
        return $this->getServiceContainer()->getDatabase();
    }

    /**
     * Register all listeners.
     *
     * @return void
     */
    protected function registerEventsInDispatcher()
    {
        // No op.
    }

    /**
     * Register multiple event listeners.
     *
     * @param string   $eventName The event name to register.
     *
     * @param callable $listener  The listener to register.
     *
     * @param int      $priority  The priority.
     *
     * @return BaseSubscriber
     */
    public function addListener($eventName, $listener, $priority = 200)
    {
        $dispatcher = $this->getServiceContainer()->getEventDispatcher();
        $dispatcher->addListener($eventName, $listener, $priority);

        return $this;
    }

    /**
     * Retrieve the MetaModel with the given id.
     *
     * @param int $modelId The model being processed.
     *
     * @return IMetaModel
     */
    protected function getMetaModelById($modelId)
    {
        $services     = $this->getServiceContainer();
        $modelFactory = $services->getFactory();
        $name         = $modelFactory->translateIdToMetaModelName($modelId);

        return $modelFactory->getMetaModel($name);
    }
}


<?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 Contao\Input;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefaultModelRelationshipDefinition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\ModelRelationshipDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\FilterBuilder;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\ParentChildCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\ParentChildConditionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\RootCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\RootConditionInterface;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;

/**
 * This class is the abstract base for the condition builders.
 */
abstract class AbstractConditionBuilder
{
    /**
     * The container to populate.
     *
     * @var IMetaModelDataDefinition
     */
    protected $container;

    /**
     * The input screen.
     *
     * @var array
     */
    protected $inputScreen;

    /**
     * The model relationship interface.
     *
     * @var ModelRelationshipDefinitionInterface
     */
    protected $definition;

    /**
     * Parse the correct conditions.
     *
     * @param IMetaModelDataDefinition $container   The data container.
     * @param array                    $inputScreen The input screen.
     *
     * @return void
     *
     * @throws \InvalidArgumentException When the stored definition does not implement the correct interface.
     */
    public static function calculateConditions(IMetaModelDataDefinition $container, array $inputScreen)
    {
        if ($container->hasDefinition(ModelRelationshipDefinitionInterface::NAME)) {
            $definition = $container->getDefinition(ModelRelationshipDefinitionInterface::NAME);
        } else {
            $definition = new DefaultModelRelationshipDefinition();

            $container->setDefinition(ModelRelationshipDefinitionInterface::NAME, $definition);
        }

        if (!$definition instanceof ModelRelationshipDefinitionInterface) {
            throw new \InvalidArgumentException('Search element does not implement the correct interface.');
        }

        $instance = new static();

        $instance->container   = $container;
        $instance->inputScreen = $inputScreen;
        $instance->definition  = $definition;
        $instance->calculate();
    }

    /**
     * The real calculating function.
     *
     * @return void
     *
     * @throws \RuntimeException When the conditions can not be determined.
     */
    abstract protected function calculate();

    /**
     * Parse the correct conditions for a MetaModel with variant support.
     *
     * @return void
     */
    protected function addParentCondition()
    {
        if ($this->inputScreen['meta']['rendertype'] === 'standalone') {
            return;
        }

        $setter  = [['to_field' => 'pid', 'from_field' => 'id']];
        $inverse = [];

        /** @var ParentChildConditionInterface $relationship */
        $relationship = $this->definition->getChildCondition(
            $this->inputScreen['meta']['ptable'],
            $this->container->getName()
        );
        if (!$relationship instanceof ParentChildConditionInterface) {
            $relationship = new ParentChildCondition();
            $relationship
                ->setSourceName($this->inputScreen['meta']['ptable'])
                ->setDestinationName($this->container->getName());
            $this->definition->addChildCondition($relationship);
        } else {
            $setter  = array_merge_recursive($setter, $relationship->getSetters());
            $inverse = array_merge_recursive($inverse, $relationship->getInverseFilterArray());
        }

        // For tl_ prefix, the only unique target can be the id?
        // maybe load parent dc and scan for unique in config then.
        $relationship
            ->setFilterArray(
                FilterBuilder::fromArray($relationship->getFilterArray())
                    ->getFilter()
                    ->andRemotePropertyEquals('pid', 'id')
                    ->getAllAsArray()
            )
            ->setSetters($setter)
            ->setInverseFilterArray($inverse);
    }

    /**
     * Parse the correct conditions for a MetaModel with variant support.
     *
     * @return void
     */
    protected function addHierarchicalConditions()
    {
        // Not hierarchical? Get out.
        if ($this->container->getBasicDefinition()->getMode() !== BasicDefinitionInterface::MODE_HIERARCHICAL) {
            return;
        }

        $relationship = $this->getRootCondition();

        // NOTE: this might bear problems when the definition will get serialized as the input value will not change.
        if (Input::get('pid')) {
            $parentValue = ModelId::fromSerialized(Input::get('pid'))->getId();
        } else {
            $parentValue = '0';
        }

        if (!$relationship->getSetters()) {
            $relationship
                ->setSetters([['property' => 'pid', 'value' => $parentValue]]);
        }

        $builder = FilterBuilder::fromArrayForRoot((array) $relationship->getFilterArray())->getFilter();

        $builder->andPropertyEquals('pid', $parentValue);

        $relationship
            ->setFilterArray($builder->getAllAsArray());

        $setter  = [['to_field' => 'pid', 'from_field' => 'id']];
        $inverse = [];

        /** @var ParentChildConditionInterface $relationship */
        $relationship = $this->definition->getChildCondition($this->container->getName(), $this->container->getName());
        if ($relationship === null) {
            $relationship = new ParentChildCondition();
            $relationship
                ->setSourceName($this->container->getName())
                ->setDestinationName($this->container->getName());
            $this->definition->addChildCondition($relationship);
        } else {
            $setter  = array_merge_recursive($setter, $relationship->getSetters());
            $inverse = array_merge_recursive($inverse, $relationship->getInverseFilterArray());
        }

        // For tl_ prefix, the only unique target can be the id?
        // maybe load parent dc and scan for unique in config then.
        $relationship
            ->setFilterArray(
                FilterBuilder::fromArray($relationship->getFilterArray())
                    ->getFilter()
                    ->andRemotePropertyEquals('pid', 'id')
                    ->getAllAsArray()
            )
            ->setSetters($setter)
            ->setInverseFilterArray($inverse);
    }

    /**
     * Parse the correct conditions for a MetaModel with variant support.
     *
     * @return RootConditionInterface
     */
    protected function getRootCondition()
    {
        $rootProvider = $this->container->getName();

        if (($relationship = $this->definition->getRootCondition()) === null) {
            $relationship = new RootCondition();
            $relationship
                ->setSourceName($rootProvider);
            $this->definition->setRootCondition($relationship);
        }

        return $relationship;
    }
}


<?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     Ingolf Steinhardt <info@e-spin.de>
 * @author     Frank Mueller <frank.mueller@linking-you.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\Attribute;

use Contao\System;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
use MetaModels\Filter\Rules\SimpleQuery;
use MetaModels\IMetaModel;

/**
 * This is the MetaModelAttribute class for handling translated attributes that reference another table.
 */
abstract class TranslatedReference extends BaseComplex implements ITranslated
{
    /**
     * Database connection.
     *
     * @var Connection
     */
    protected $connection;

    /**
     * Instantiate an MetaModel attribute.
     *
     * Note that you should not use this directly but use the factory classes to instantiate attributes.
     *
     * @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
     *
     * @param array      $arrData      The information array, for attribute information, refer to documentation of
     *                                 table tl_metamodel_attribute and documentation of the certain attribute classes
     *                                 for information what values are understood.
     *
     * @param Connection $connection   Database connection.
     */
    public function __construct(IMetaModel $objMetaModel, $arrData = [], Connection $connection = null)
    {
        parent::__construct($objMetaModel, $arrData);

        if (null === $connection) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Connection is missing. It has to be passed in the constructor. Fallback will be dropped.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
            $connection = System::getContainer()->get('database_connection');
        }

        $this->connection = $connection;
    }

    /**
     * Retrieve the name of the table that contains the data for this reference.
     *
     * @return string
     */
    abstract protected function getValueTable();

    /**
     * Build a where clause for the given id(s) and language code.
     *
     * @param QueryBuilder         $queryBuilder The query builder for the query  being build.
     *
     * @param string[]|string|null $mixIds       One, none or many ids to use.
     *
     * @param string|string[]      $mixLangCode  The language code/s to use, optional.
     *
     * @return void
     */
    private function buildWhere(QueryBuilder $queryBuilder, $mixIds, $mixLangCode = '')
    {
        $queryBuilder
            ->andWhere('att_id = :att_id')
            ->setParameter('att_id', $this->get('id'));

        if (!empty($mixIds)) {
            if (is_array($mixIds)) {
                $queryBuilder
                    ->andWhere('item_id IN (:item_ids)')
                    ->setParameter('item_ids', $mixIds, Connection::PARAM_STR_ARRAY);
            } else {
                $queryBuilder
                    ->andWhere('item_id = :item_id')
                    ->setParameter('item_id', $mixIds);
            }
        }

        if (!empty($mixLangCode)) {
            if (is_array($mixLangCode)) {
                $queryBuilder
                    ->andWhere('langcode IN (:langcode)')
                    ->setParameter('langcode', $mixLangCode, Connection::PARAM_STR_ARRAY);
            } else {
                $queryBuilder
                    ->andWhere('langcode = :langcode')
                    ->setParameter('langcode', $mixLangCode);
            }
        }
    }

    /**
     * Retrieve the values to be used in the INSERT or UPDATE SQL for the given parameters.
     *
     * @param array  $arrValue    The native value of the attribute.
     *
     * @param int    $intId       The id of the item to be saved.
     *
     * @param string $strLangCode The language code of the language the value is in.
     *
     * @return array
     *
     * @throws \InvalidArgumentException When the passed value is not null and not an array.
     */
    protected function getSetValues($arrValue, $intId, $strLangCode)
    {
        if (($arrValue !== null) && !is_array($arrValue)) {
            throw new \InvalidArgumentException(sprintf('Invalid value provided: %s', var_export($arrValue, true)));
        }

        return array
        (
            'tstamp' => time(),
            'value' => (string) $arrValue['value'],
            'att_id' => $this->get('id'),
            'langcode' => $strLangCode,
            'item_id' => $intId,
        );
    }

    /**
     * Retrieve the columns to be used for key and value when retrieving the filter options.
     *
     * Returned array must contain two elements having the keys "key" and "value".
     *
     * @return array
     */
    protected function getOptionizer()
    {
        return array(
            'key' => 'value',
            'value' => 'value'
        );
    }

    /**
     * {@inheritDoc}
     */
    public function valueToWidget($varValue)
    {
        return $varValue['value'];
    }

    /**
     * {@inheritDoc}
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function widgetToValue($varValue, $itemId)
    {
        return array
        (
            'tstamp' => time(),
            'value'  => $varValue,
            'att_id' => $this->get('id'),
        );
    }

    /**
     * {@inheritDoc}
     */
    public function getDataFor($arrIds)
    {
        $strActiveLanguage   = $this->getMetaModel()->getActiveLanguage();
        $strFallbackLanguage = $this->getMetaModel()->getFallbackLanguage();

        $arrReturn = $this->getTranslatedDataFor($arrIds, $strActiveLanguage);

        // Second round, fetch fallback languages if not all items could be resolved.
        if ((count($arrReturn) < count($arrIds)) && ($strActiveLanguage != $strFallbackLanguage)) {
            $arrFallbackIds = array();
            foreach ($arrIds as $intId) {
                if (empty($arrReturn[$intId])) {
                    $arrFallbackIds[] = $intId;
                }
            }

            if ($arrFallbackIds) {
                $arrFallbackData = $this->getTranslatedDataFor($arrFallbackIds, $strFallbackLanguage);
                // Cannot use array_merge here as it would renumber the keys.
                foreach ($arrFallbackData as $intId => $arrValue) {
                    $arrReturn[$intId] = $arrValue;
                }
            }
        }
        return $arrReturn;
    }

    /**
     * Determine the available languages.
     *
     * @return null|\string[]
     *
     * @throws \RuntimeException When an untranslated MetaModel is encountered.
     */
    private function determineLanguages()
    {
        $languages = $this->getMetaModel()->getAvailableLanguages();
        if ($languages === null) {
            throw new \RuntimeException(
                'MetaModel ' . $this->getMetaModel()->getName() . ' does not seem to be translated.'
            );
        }
        return $languages;
    }

    /**
     * {@inheritDoc}
     */
    public function setDataFor($arrValues)
    {
        foreach ($this->determineLanguages() as $strLangCode) {
            $this->setTranslatedDataFor($arrValues, $strLangCode);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function unsetDataFor($arrIds)
    {
        foreach ($this->determineLanguages() as $strLangCode) {
            $this->unsetValueFor($arrIds, $strLangCode);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function searchFor($strPattern)
    {
        return $this->searchForInLanguages($strPattern, array($this->getMetaModel()->getActiveLanguage()));
    }

    /**
     * {@inheritDoc}
     */
    public function searchForInLanguages($strPattern, $arrLanguages = array())
    {
        $optionizer = $this->getOptionizer();
        $procedure  = $optionizer['value'] . ' LIKE :pattern';
        $strPattern = str_replace(array('*', '?'), array('%', '_'), $strPattern);

        $queryBuilder = $this->connection->createQueryBuilder()
            ->select('DISTINCT item_id')
            ->from($this->getValueTable())
            ->andWhere($procedure)
            ->setParameter('pattern', $strPattern);

        $this->buildWhere($queryBuilder, null, $arrLanguages);

        $filterRule = SimpleQuery::createFromQueryBuilder($queryBuilder, 'item_id');

        return $filterRule->getMatchingIds();
    }

    /**
     * {@inheritDoc}
     */
    public function sortIds($idList, $strDirection)
    {
        $langSet = sprintf(
            '\'%s\',\'%s\'',
            $this->getMetaModel()->getActiveLanguage(),
            $this->getMetaModel()->getFallbackLanguage()
        );

        $statement = $this->connection
            ->executeQuery(
                sprintf(
                    'SELECT t1.item_id
                       FROM %1$s AS t1
                       RIGHT JOIN %1$s ON (t1.id = (SELECT
                           t2.id
                           FROM %1$s AS t2
                           WHERE (t2.att_id=%2$s)
                           AND langcode IN (%3$s)
                           AND (t2.item_id=t1.item_id)
                           ORDER BY FIELD(t2.langcode,%3$s)
                           LIMIT 1
                       ))
                       WHERE t1.id IS NOT NULL
                       AND  (t1.item_id IN (?))
                       GROUP BY t1.id
                       ORDER BY t1.value %4$s',
                    // @codingStandardsIgnoreStart - we want to keep the numbers at the end of the lines below.
                    $this->getValueTable(),                    // 1
                    $this->get('id'),                          // 2
                    $langSet,                                  // 3
                    $strDirection                              // 4
                    // @codingStandardsIgnoreEnd
                ),
                [$idList],
                [Connection::PARAM_STR_ARRAY]
            );

        return $statement->fetchAll(\PDO::FETCH_COLUMN, 0);
    }

    /**
     * {@inheritDoc}
     */
    public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
    {
        $queryBuilder = $this->connection->createQueryBuilder()
            ->select('*')
            ->from($this->getValueTable());

        $this->buildWhere($queryBuilder, $idList, $this->getMetaModel()->getActiveLanguage());

        $statement     = $queryBuilder->execute();
        $arrOptionizer = $this->getOptionizer();

        $arrReturn = array();
        while ($objValue = $statement->fetch(\PDO::FETCH_OBJ)) {
            $arrReturn[$objValue->{$arrOptionizer['key']}] = $objValue->{$arrOptionizer['value']};
        }
        return $arrReturn;
    }

    /**
     * {@inheritDoc}
     */
    public function setTranslatedDataFor($arrValues, $strLangCode)
    {
        // First off determine those to be updated and those to be inserted.
        $arrIds      = array_keys($arrValues);
        $arrExisting = $this->fetchExistingIdsFor($arrIds, $strLangCode);
        $arrNewIds   = array_diff($arrIds, $arrExisting);

        // Update existing values - delete if empty.
        foreach ($arrExisting as $intId) {
            $queryBuilder = $this->connection->createQueryBuilder();
            $this->buildWhere($queryBuilder, $intId, $strLangCode);

            if ($arrValues[$intId]['value'] != '') {
                $queryBuilder->update($this->getValueTable());

                foreach ($this->getSetValues($arrValues[$intId], $intId, $strLangCode) as $name => $value) {
                    $queryBuilder
                        ->set($name, ':' . $name)
                        ->setParameter($name, $value);
                }
            } else {
                $queryBuilder->delete($this->getValueTable());
            }

            $queryBuilder->execute();
        }

        // Insert the new values.
        foreach ($arrNewIds as $intId) {
            if ($arrValues[$intId]['value'] == '') {
                continue;
            }

            $this->connection->insert(
                $this->getValueTable(),
                $this->getSetValues($arrValues[$intId], $intId, $strLangCode)
            );
        }
    }

    /**
     * Filter the item ids for ids that exist in the database.
     *
     * @param array  $idList   The id list.
     * @param string $langCode The language code.
     *
     * @return string[]
     */
    protected function fetchExistingIdsFor($idList, $langCode)
    {
        $queryBuilder = $this
            ->connection
            ->createQueryBuilder()
            ->select('item_id')
            ->from($this->getValueTable());
        $this->buildWhere($queryBuilder, $idList, $langCode);

        $statement = $queryBuilder->execute();

        return $statement->fetchAll(\PDO::FETCH_COLUMN);
    }

    /**
     * {@inheritDoc}
     */
    public function getTranslatedDataFor($arrIds, $strLangCode)
    {
        $queryBuilder = $this->connection->createQueryBuilder()
            ->select('*')
            ->from($this->getValueTable());

        $this->buildWhere($queryBuilder, $arrIds, $strLangCode);

        $statement = $queryBuilder->execute();
        $arrReturn = array();
        while ($value = $statement->fetch(\PDO::FETCH_ASSOC)) {
            /** @noinspection PhpUndefinedFieldInspection */
            $arrReturn[$value['item_id']] = $value;
        }
        return $arrReturn;
    }

    /**
     * {@inheritDoc}
     */
    public function unsetValueFor($arrIds, $strLangCode)
    {
        $queryBuilder = $this->connection->createQueryBuilder()->delete($this->getValueTable());
        $this->buildWhere($queryBuilder, $arrIds, $strLangCode);

        $queryBuilder->execute();
    }
}