<?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     David Molineus <david.molineus@netzmacht.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\MetaModel;

use ContaoCommunityAlliance\DcGeneral\Clipboard\ClipboardInterface;
use ContaoCommunityAlliance\DcGeneral\Clipboard\Filter;
use ContaoCommunityAlliance\DcGeneral\Clipboard\ItemInterface;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPasteButtonEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPasteRootButtonEvent;
use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface;
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use MetaModels\IFactory;

/**
 * This class handles the paste into and after button activation and deactivation for all MetaModels being edited.
 */
class PasteButtonListener
{
    /**
     * The factory.
     *
     * @var IFactory
     */
    private $factory;

    /**
     * The current environment.
     *
     * @var EnvironmentInterface
     */
    private $environment;

    /**
     * The current data provider.
     *
     * @var DataProviderInterface
     */
    private $provider;

    /**
     * The name of current data provider.
     *
     * @var string
     */
    private $providerName;

    /**
     * The model where we have to check if is it a paste into or paste after.
     *
     * @var ModelInterface
     */
    private $currentModel;

    /**
     * Get determinator if there exists a circular reference.
     *
     * This flag determines if there exists a circular reference between the item currently in the clipboard and the
     * current model. A circular reference is of relevance when performing a cut and paste operation for example.
     *
     * @var boolean
     */
    private $circularReference;

    /**
     * Disable the paste after.
     *
     * @var bool
     */
    private $disablePA = true;

    /**
     * Disable the paste into.
     *
     * @var bool
     */
    private $disablePI = true;

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

    /**
     * Handle the paste into and after buttons.
     *
     * @param GetPasteButtonEvent $event The event.
     *
     * @return void
     *
     * @throws \RuntimeException When more than one model is contained within the clipboard.
     */
    public function handle(GetPasteButtonEvent $event)
    {
        $this->circularReference = $event->isCircularReference();
        $this->environment       = $event->getEnvironment();
        $this->provider          = $this->environment->getDataProvider();
        $this->providerName      = $this->provider->getEmptyModel()->getProviderName();
        $clipboard               = $this->environment->getClipboard();
        $this->currentModel      = $event->getModel();
        $this->disablePI         = true;
        $this->disablePA         = true;

        // Only run for a MetaModels and if both values already disabled return here.
        if ((substr($this->providerName, 0, 3) !== 'mm_')
            || ($event->isPasteIntoDisabled() && $event->isPasteAfterDisabled())
        ) {
            return;
        }

        $this->checkForAction($clipboard, 'copy');
        $this->checkForAction($clipboard, 'create');
        $this->checkForAction($clipboard, 'cut');

        $event
            ->setPasteAfterDisabled($this->disablePA)
            ->setPasteIntoDisabled($this->disablePI);
    }

    /**
     * Handle the paste into and after buttons.
     *
     * @param GetPasteRootButtonEvent $event The event.
     *
     * @return void
     *
     * @throws \RuntimeException When more than one model is contained within the clipboard.
     */
    public function handleRoot(GetPasteRootButtonEvent $event)
    {
        $this->environment  = $event->getEnvironment();
        $this->provider     = $this->environment->getDataProvider();
        $this->providerName = $this->provider->getEmptyModel()->getProviderName();
        $clipboard          = $this->environment->getClipboard();
        $this->currentModel = null;
        $this->disablePI    = false;

        // Only run for a MetaModels.
        if ((substr($this->providerName, 0, 3) !== 'mm_') || $event->isPasteDisabled()) {
            return;
        }

        $this->checkForAction($clipboard, 'copy');
        $this->checkForAction($clipboard, 'create');
        $this->checkForAction($clipboard, 'cut');

        $event->setPasteDisabled($this->disablePI);
    }

    /**
     * Find a item by its id.
     *
     * @param int $modelId The id to find.
     *
     * @return ModelInterface
     */
    private function getModelById($modelId)
    {
        if ($modelId === null) {
            return null;
        }

        $provider = $this->environment->getDataProvider();
        $config   = $provider
            ->getEmptyConfig()
            ->setId($modelId);

        return $provider->fetch($config);
    }

    /**
     * Determines if this MetaModel instance is subject to variant handling.
     *
     * @return bool true if variants are handled, false otherwise.
     *
     * @throws \RuntimeException When the MetaModel can not be loaded.
     */
    private function hasVariants()
    {
        $metaModel = $this->factory->getMetaModel($this->providerName);

        if ($metaModel === null) {
            throw new \RuntimeException(sprintf('Could not find a MetaModels with the name %s', $this->providerName));
        }

        return $metaModel->hasVariants();
    }

    /**
     * Check the buttons based on the action.
     *
     * @param ClipboardInterface $clipboard The clipboard.
     *
     * @param string             $action    The action to be checked.
     *
     * @return void
     */
    private function checkForAction($clipboard, $action)
    {
        // Make a filter for the given action.
        $filter = new Filter();
        $filter->andActionIs($action);
        $items = $clipboard->fetch($filter);

        // Check if there are items.
        if ($items === null) {
            return;
        }

        /** @var ItemInterface[] $items */
        foreach ($items as $item) {
            // Check the context.
            $itemProviderName = $item->getDataProviderName();
            $modelId          = $item->getModelId();

            if ($this->providerName !== $itemProviderName) {
                continue;
            }

            if (!$modelId) {
                $this->checkEmpty($action);
                continue;
            }

            $containedModel = $this->getModelById($modelId->getId());
            if ($this->currentModel == null) {
                $this->checkForRoot($containedModel, $action);
            } elseif ($containedModel) {
                $this->checkForModel($containedModel, $action);
            } else {
                $this->checkEmpty($action);
            }
        }
    }

    /**
     * Check the PA and PI without a contained model.
     *
     * @param string $action The action to be checked.
     *
     * @return void
     */
    private function checkEmpty($action)
    {
        if ($this->hasVariants() && $this->currentModel !== null) {
            $this->disablePA = false;
        } elseif ($action == 'create') {
            $this->disablePA = false;
            $this->disablePI = false;
        }
    }

    /**
     * Check the PI for the root element.
     *
     * @param ModelInterface $containedModel The model with all data.
     *
     * @param string         $action         The action to be checked.
     *
     * @return void
     */
    private function checkForRoot($containedModel, $action)
    {
        if ($this->hasVariants() && $action == 'cut' && $containedModel->getProperty('varbase') == 0) {
            $this->disablePI = true;
        }
    }

    /**
     * Check the PA and PI with a model.
     *
     * @param ModelInterface $containedModel The model with all data.
     *
     * @param string         $action         The action to be checked.
     *
     * @return void
     */
    private function checkForModel($containedModel, $action)
    {
        if (!$this->circularReference) {
            if ($this->hasVariants()) {
                $this->checkModelWithVariants($containedModel);
            }
            $this->checkModelWithoutVariants($containedModel);
        } elseif ($this->currentModel == null && $containedModel->getProperty('varbase') == 0) {
            $this->disablePA = true;
        } else {
            $this->disablePA = false;
            // The following rules apply:
            // 1. Variant bases must not get pasted into anything.
            // 2. If we are not in create mode, disable the paste into for the item itself.
            $this->disablePI =
                ($this->hasVariants() && $containedModel->getProperty('varbase') == 1)
                || ($action != 'create' && $containedModel->getId() == $this->currentModel->getId());
        }
    }

    /**
     * Check the PA and PI with a model and variant support.
     *
     * @param ModelInterface $containedModel The model to check.
     *
     * @return void
     */
    private function checkModelWithVariants($containedModel)
    {
        // Item and variant support.
        $isVarbase        = (bool) $containedModel->getProperty('varbase');
        $vargroup         = $containedModel->getProperty('vargroup');
        $isCurrentVarbase = (bool) $this->currentModel->getProperty('varbase');
        $currentVargroup  = $this->currentModel->getProperty('vargroup');

        if ($isVarbase && !$this->circularReference && $isCurrentVarbase) {
            // Insert new items only after bases.
            // Insert a varbase after any other varbase, for sorting.
            $this->disablePA = false;
        } elseif (!$isVarbase && !$isCurrentVarbase && $vargroup == $currentVargroup) {
            // Move items in their vargroup and only there.
            $this->disablePA = false;
        }

        $this->disablePI = !$isCurrentVarbase || $isVarbase;
    }

    /**
     * Check the PA and PI with a model and a normal flat build.
     *
     * @param ModelInterface $containedModel The model to check.
     *
     * @return void
     */
    private function checkModelWithoutVariants($containedModel)
    {
        $parentDefinition = $this->environment->getDataDefinition()->getBasicDefinition()->getParentDataProvider();

        $this->disablePA = ($this->currentModel->getId() == $containedModel->getId())
            || ($parentDefinition && $this->currentModel->getProperty('pid') == $containedModel->getProperty('pid'));
        $this->disablePI = ($this->circularReference)
            || ($this->currentModel->getId() == $containedModel->getId())
            || ($parentDefinition && $this->currentModel->getProperty('pid') == $containedModel->getId());
    }
}


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

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractEnvironmentAwareEvent;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractModelAwareEvent;
use Doctrine\DBAL\Connection;
use MetaModels\IFactory;

/**
 * This provides a way to obtain a MetaModel.
 */
abstract class AbstractListener
{
    /**
     * The scope determinator.
     *
     * @var RequestScopeDeterminator
     */
    private $scopeDeterminator;

    /**
     * The MetaModel factory.
     *
     * @var IFactory
     */
    protected $factory;

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

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

    /**
     * Retrieve the MetaModel attached to the model condition setting.
     *
     * @param EnvironmentInterface $interface The environment.
     *
     * @return \MetaModels\IMetaModel
     *
     * @throws \RuntimeException Throws if could not retrieve metamodel.
     */
    public function getMetaModel(EnvironmentInterface $interface)
    {
        $metaModelId = $this->connection->createQueryBuilder()
            ->select('d.pid')
            ->from('tl_metamodel_dca', 'd')
            ->leftJoin('d', 'tl_metamodel_dcasetting', 's', '(d.id=s.pid)')
            ->where('(s.id=:id)')
            ->setParameter('id', ModelId::fromSerialized($interface->getInputProvider()->getParameter('pid'))->getId())
            ->execute();

        if ($tableName = $this->factory->translateIdToMetaModelName($metaModelId = $metaModelId->fetchColumn())) {
            return $this->factory->getMetaModel($tableName);
        }

        throw new \RuntimeException('Could not retrieve MetaModel ' . $metaModelId);
    }

    /**
     * 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_dcasetting_condition' !== $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>
 * @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;

use Doctrine\Common\Cache\Cache;
use MetaModels\Attribute\IAttributeFactory;
use MetaModels\Filter\Setting\IFilterSettingFactory;
use MetaModels\Render\Setting\IRenderSettingFactory;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Reference implementation of IMetaModelsServiceContainer.
 *
 * @deprecated The service container will get removed, use the symfony service container instead.
 */
class MetaModelsServiceContainer implements IMetaModelsServiceContainer
{
    /**
     * The factory to use.
     *
     * @var IFactory
     */
    protected $factory;

    /**
     * The factory to use.
     *
     * @var IAttributeFactory
     */
    protected $attributeFactory;

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

    /**
     * The render setting factory.
     *
     * @var IRenderSettingFactory
     */
    protected $renderFactory;

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

    /**
     * The Contao database instance to use.
     *
     * @var \Contao\Database
     */
    protected $database;

    /**
     * The cache in use.
     *
     * @var Cache
     */
    protected $cache;

    /**
     * Registered services.
     *
     * @var object[]
     */
    protected $services;

    /**
     * Set the factory to use.
     *
     * @param IFactory|callable $factory The factory in use.
     *
     * @return MetaModelsServiceContainer
     */
    public function setFactory($factory)
    {
        $this->factory = $factory;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getFactory()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->factory)) {
            $this->factory = \call_user_func($this->factory);
            $this->factory->setServiceContainer($this, false);
        }

        return $this->factory;
    }

    /**
     * Set the factory to use.
     *
     * @param IAttributeFactory|callable $factory The factory in use.
     *
     * @return MetaModelsServiceContainer
     */
    public function setAttributeFactory($factory)
    {
        $this->attributeFactory = $factory;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getAttributeFactory()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->attributeFactory)) {
            $this->attributeFactory = \call_user_func($this->attributeFactory);
            $this->attributeFactory->setServiceContainer($this, false);
        }

        return $this->attributeFactory;
    }

    /**
     * Set the filter setting factory.
     *
     * @param IFilterSettingFactory|callable $filterFactory The factory.
     *
     * @return MetaModelsServiceContainer
     */
    public function setFilterFactory($filterFactory)
    {
        $this->filterFactory = $filterFactory;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getFilterFactory()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->filterFactory)) {
            $this->filterFactory = \call_user_func($this->filterFactory);
            $this->filterFactory->setServiceContainer($this, false);
        }

        return $this->filterFactory;
    }

    /**
     * Set the filter setting factory.
     *
     * @param IRenderSettingFactory|callable $renderFactory The factory.
     *
     * @return MetaModelsServiceContainer
     */
    public function setRenderSettingFactory($renderFactory)
    {
        $this->renderFactory = $renderFactory;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getRenderSettingFactory()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->renderFactory)) {
            $this->renderFactory = \call_user_func($this->renderFactory);
            $this->renderFactory->setServiceContainer($this, false);
        }

        return $this->renderFactory;
    }

    /**
     * Set the event dispatcher.
     *
     * @param EventDispatcherInterface|callable $dispatcher The event dispatcher.
     *
     * @return MetaModelsServiceContainer
     */
    public function setEventDispatcher($dispatcher)
    {
        $this->dispatcher = $dispatcher;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getEventDispatcher()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->dispatcher)) {
            $this->dispatcher = \call_user_func($this->dispatcher);
        }

        return $this->dispatcher;
    }

    /**
     * Set the Contao database instance.
     *
     * @param \Contao\Database|callable $database The contao database instance.
     *
     * @return MetaModelsServiceContainer
     */
    public function setDatabase($database)
    {
        $this->database = $database;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getDatabase()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->database)) {
            $this->database = \call_user_func($this->database);
        }

        return $this->database;
    }

    /**
     * Retrieve the cache to use.
     *
     * @return Cache
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getCache()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        if (\is_callable($this->cache)) {
            $this->cache = \call_user_func($this->cache);
        }

        return $this->cache;
    }

    /**
     * Set the cache to use.
     *
     * @param Cache|callable $cache The cache instance.
     *
     * @return MetaModelsServiceContainer
     */
    public function setCache($cache)
    {
        $this->cache = $cache;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \InvalidArgumentException When the passed service is not an object and no service name has been passed.
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function setService($service, $serviceName = null)
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd
        if ($serviceName === null) {
            if (!\is_object($service) || $service instanceof \Closure) {
                throw new \InvalidArgumentException(
                    'Service name must be given to ' . __CLASS__ . '::setService when not passing a class instance.'
                );
            }

            $serviceName = \get_class($service);
        }

        $this->services[$serviceName] = $service;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getService($serviceName)
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated as the service container will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        // Hacked in here as initialization is dead now.
        if (!isset($this->services[(string) $serviceName]) && 'metamodels-view-combinations' === $serviceName) {
            $determinator = \System::getContainer()->get('cca.dc-general.scope-matcher');
            switch (true) {
                case $determinator->currentScopeIsFrontend():
                    $this->services['metamodels-view-combinations'] =
                        new \MetaModels\FrontendIntegration\ViewCombinations(
                            $this,
                            $GLOBALS['container']['user'],
                            \System::getContainer()->get('database_connection')
                        );
                    break;
                case $determinator->currentScopeIsBackend():
                    $this->services['metamodels-view-combinations'] =
                        new \MetaModels\BackendIntegration\ViewCombinations(
                            $this,
                            $GLOBALS['container']['user'],
                            \System::getContainer()->get('database_connection')
                        );
                    break;
                default:
            }
        }

        return ($this->services[(string) $serviceName] ?? null);
    }
}


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\DefinitionBuilder;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\ConditionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefaultPalettesDefinition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\PalettesDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\Properties\PropertyInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Palette\DefaultPaletteCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\BooleanCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionChain;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Legend;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Palette;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Property;
use MetaModels\CoreBundle\DcGeneral\PropertyConditionFactory;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\DcGeneral\DataDefinition\Palette\Condition\Property\IsVariantAttribute;
use MetaModels\IFactory;
use MetaModels\IMetaModel;
use MetaModels\ViewCombination\ViewCombination;

/**
 * This class takes care of the palette building.
 */
class PaletteBuilder
{
    use MetaModelDefinitionBuilderTrait;

    /**
     * The view combinations.
     *
     * @var ViewCombination
     */
    private $viewCombination;

    /**
     * The factory to use.
     *
     * @var IFactory
     */
    private $factory;

    /**
     * The property condition factory.
     *
     * @var PropertyConditionFactory
     */
    private $conditionFactory;

    /**
     * Create a new instance.
     *
     * @param ViewCombination          $viewCombination  The view combinations.
     * @param IFactory                 $factory          The factory.
     * @param PropertyConditionFactory $conditionFactory The condition factory.
     */
    public function __construct(
        ViewCombination $viewCombination,
        IFactory $factory,
        PropertyConditionFactory $conditionFactory
    ) {
        $this->viewCombination  = $viewCombination;
        $this->factory          = $factory;
        $this->conditionFactory = $conditionFactory;
    }

    /**
     * Parse and build the backend view definition for the old Contao2 backend view.
     *
     * @param IMetaModelDataDefinition $container The data container.
     *
     * @return void
     */
    protected function build(IMetaModelDataDefinition $container)
    {
        if (null === ($inputScreen = $this->viewCombination->getScreen($container->getName()))) {
            return;
        }
        $metaModel          = $this->factory->getMetaModel($container->getName());
        $variantHandling    = $metaModel->hasVariants();
        $palettesDefinition = $this->getOrCreatePaletteDefinition($container);

        $properties = $container->getPropertiesDefinition();

        $palettesDefinition->addPalette($palette = new Palette());
        $palette
            ->setName('default')
            ->setCondition(new DefaultPaletteCondition());

        foreach ($inputScreen['legends'] as $legendName => $legendInfo) {
            $legend = new Legend($legendName);
            $legend->setInitialVisibility(!$legendInfo['hide']);
            $palette->addLegend($legend);

            $legendConditions = $this->buildCondition($legendInfo['condition'], $metaModel);
            foreach ($legendInfo['properties'] as $property) {
                $legend->addProperty(
                    $this->createProperty(
                        $properties->getProperty($property['name']),
                        $property['name'],
                        $variantHandling,
                        $this->buildCondition($property['condition'], $metaModel),
                        $legendConditions
                    )
                );
            }
        }
    }

    /**
     * Retrieve or create the palette definition.
     *
     * @param IMetaModelDataDefinition $container The container.
     *
     * @return DefaultPalettesDefinition|\ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefinitionInterface
     */
    private function getOrCreatePaletteDefinition(IMetaModelDataDefinition $container)
    {
        if ($container->hasDefinition(PalettesDefinitionInterface::NAME)) {
            return $container->getDefinition(PalettesDefinitionInterface::NAME);
        }
        $container->setDefinition(
            PalettesDefinitionInterface::NAME,
            $palettesDefinition = new DefaultPalettesDefinition()
        );

        return $palettesDefinition;
    }

    /**
     * Create a property for the palette.
     *
     * @param PropertyInterface       $property        The input screen.
     * @param string                  $propertyName    The property name.
     * @param bool                    $variantHandling The MetaModel instance.
     * @param ConditionInterface|null $condition       The condition.
     * @param ConditionInterface|null $legendCondition The condition.
     *
     * @return Property
     */
    private function createProperty(
        PropertyInterface $property,
        $propertyName,
        $variantHandling,
        ConditionInterface $condition = null,
        ConditionInterface $legendCondition = null
    ) {
        $paletteProperty = new Property($propertyName);

        $extra = $property->getExtra();

        $chain = new PropertyConditionChain();
        $paletteProperty->setEditableCondition($chain);
        if (isset($extra['readonly'])) {
            $chain->addCondition(new BooleanCondition($extra['readonly']));
        }

        $chain = new PropertyConditionChain();
        $paletteProperty->setVisibleCondition($chain);
        // If variants, do show only if allowed.
        if ($variantHandling) {
            $chain->addCondition(new IsVariantAttribute());
        }
        $chain->addCondition(
            new BooleanCondition(
                !((isset($extra['doNotShow']) && $extra['doNotShow'])
                || (isset($extra['hideInput']) && $extra['hideInput']))
            )
        );

        if (null !== $condition) {
            $chain->addCondition($condition);
        }
        if (null !== $legendCondition) {
            $chain->addCondition($legendCondition);
        }

        return $paletteProperty;
    }

    /**
     * Build the conditions for the passed condition array.
     *
     * @param array|null $condition The condition information.
     *
     * @param IMetaModel $metaModel The MetaModel instance.
     *
     * @return null|ConditionInterface
     *
     * @throws \RuntimeException Throws if condition type not be transformed to an instance.
     */
    private function buildCondition($condition, $metaModel)
    {
        if (null === $condition) {
            return null;
        }

        return $this->conditionFactory->createCondition($condition, $metaModel);
    }
}


<?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     Andreas Isaak <info@andreas-isaak.de>
 * @author     Stefan Heimes <stefan_heimes@hotmail.com>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\BackendIntegration;

use ContaoCommunityAlliance\DcGeneral\Action;
use ContaoCommunityAlliance\DcGeneral\DataContainerInterface;
use ContaoCommunityAlliance\DcGeneral\Contao\Callback\Callbacks;

/**
 * Implementation of the MetaModel Backend Module that allowing access to MetaModel configuration etc. Everything below
 * http://..../contao?do=metamodels&.... ends up here.
 */
class Module
{
    /**
     * The data container.
     *
     * @var DataContainerInterface
     */
    private $dataContainer;

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

    /**
     * Parse the template.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function generate()
    {
        $GLOBALS['TL_CSS'][] = 'bundles/metamodelscore/css/style.css';
        $arrModule           = $GLOBALS['BE_MOD']['metamodels']['metamodels'];
        // Custom action (if key is not defined in config.php the default action will be called).
        if (\Input::get('key') && isset($arrModule[\Input::get('key')])) {
            Callbacks::call($arrModule[\Input::get('key')], $this, $arrModule);
        }

        $act = \Input::get('act');
        if (!strlen($act)) {
            $act = 'showAll';
        }

        return $this->dataContainer->getEnvironment()->getController()->handle(new Action($act));
    }
}


<?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 ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\Event\PostPersistModelEvent;

/**
 * This class takes care of updating all data when an attribute has been saved.
 */
class AttributeSavedListener extends BaseListener
{
    /**
     * Handle the update of an attribute and all attached data.
     *
     * @param PostPersistModelEvent $event The event.
     *
     * @return void
     */
    public function handle(PostPersistModelEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $old         = $event->getOriginalModel();
        $new         = $event->getModel();
        $oldInstance = $old->getProperty('pid') ? $this->createAttributeInstance($old) : null;
        $newInstance = $this->createAttributeInstance($new);

        // If type or column name has been changed, destroy old data and initialize new.
        if ($this->isAttributeNameOrTypeChanged($new, $old)) {
            // Destroy old instance.
            if ($oldInstance) {
                $oldInstance->destroyAUX();
            }

            // Create new instance aux info.
            if ($newInstance) {
                $newInstance->initializeAUX();
            }
        }

        if ($newInstance) {
            // Now loop over all values and update the meta in the instance.
            foreach ($new->getPropertiesAsArray() as $strKey => $varValue) {
                $newInstance->handleMetaChange($strKey, $varValue);
            }
        }
    }

    /**
     * Check if either type or column name have been changed within the model.
     *
     * @param ModelInterface $new The new model.
     * @param ModelInterface $old The old model (or null).
     *
     * @return bool
     */
    private function isAttributeNameOrTypeChanged(ModelInterface $new, ModelInterface $old)
    {
        return ($old->getProperty('type') !== $new->getProperty('type'))
            || ($old->getProperty('colname') !== $new->getProperty('colname'));
    }
}


<?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     David Molineus <david.molineus@netzmacht.de>
 * @author     Alexander Menk <a.menk@imi.de>
 * @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\BackendIntegration\InputScreen;

use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ConditionChainInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\NotCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionChain;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionInterface;
use MetaModels\Events\CreatePropertyConditionEvent;
use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;

/**
 * Implementation of IInputScreen.
 *
 * @deprecated This class will get removed.
 */
class InputScreen implements IInputScreen
{
    /**
     * The service container.
     *
     * @var IMetaModelsServiceContainer
     */
    protected $container;

    /**
     * The data for the input screen.
     *
     * @var array
     */
    protected $data;

    /**
     * The legends contained within the input screen.
     *
     * @var array
     */
    protected $legends = array();

    /**
     * The properties contained within the input screen.
     *
     * @var array
     */
    protected $properties = array();

    /**
     * The conditions.
     *
     * @var ConditionChainInterface[]
     */
    protected $conditions = array();

    /**
     * Grouping and sorting information.
     *
     * @var IInputScreenGroupingAndSorting[]
     */
    protected $groupSort = array();

    /**
     * Simple map from property setting id to property name.
     *
     * @var array
     */
    protected $propertyMap = array();

    /**
     * Simple map from property name to property setting id.
     *
     * @var array
     */
    protected $propertyMap2 = array();

    /**
     * Create a new instance.
     *
     * @param IMetaModelsServiceContainer $container    The service container.
     *
     * @param array                       $data         The information about the input screen.
     *
     * @param array                       $propertyRows The information about all contained properties.
     *
     * @param array                       $conditions   The property condition information.
     *
     * @param array                       $groupSort    The grouping and sorting information.
     */
    public function __construct($container, $data, $propertyRows, $conditions, $groupSort)
    {
        $this->data      = $data;
        $this->container = $container;

        $this->transformConditions($conditions);
        $this->translateRows($propertyRows);
        $this->transformGroupSort($groupSort);
    }

    /**
     * Transform a legend information into the property legends.
     *
     * @param array      $legend    The legend to transform.
     *
     * @param IMetaModel $metaModel The metamodel the legend belongs to.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function translateLegend($legend, $metaModel)
    {
        $arrLegend = StringUtil::deserialize($legend['legendtitle']);
        if (is_array($arrLegend)) {
            // Try to use the language string from the array.
            $strLegend = $arrLegend[$GLOBALS['TL_LANGUAGE']];
            if (!$strLegend) {
                // Use the fallback.
                $strLegend = $arrLegend[$metaModel->getFallbackLanguage()];
                if (!$strLegend) {
                    // Last resort, simply "legend". See issue #926.
                    $strLegend = 'legend' . (count($this->legends) + 1);
                }
            }
        } else {
            $strLegend = $legend['legendtitle'] ? $legend['legendtitle'] : 'legend';
        }

        $legendName = StringUtil::standardize($strLegend);

        $this->legends[$legendName] = array
        (
            'name'       => $strLegend,
            'visible'    => !(isset($legend['legendhide']) && (bool) $legend['legendhide']),
            'properties' => array()
        );

        return $legendName;
    }

    /**
     * Translate a property.
     *
     * @param array      $property  The property information to transform.
     *
     * @param IMetaModel $metaModel The MetaModel the property belongs to.
     *
     * @param string     $legend    The legend the property belongs to.
     *
     * @return bool
     */
    protected function translateProperty($property, $metaModel, $legend)
    {
        $attribute = $metaModel->getAttributeById($property['attr_id']);

        // Dead meat.
        if (!$attribute) {
            return false;
        }

        $propName = $attribute->getColName();

        $this->legends[$legend]['properties'][] = $propName;

        $this->properties[$propName] = array
        (
            'info'       => $attribute->getFieldDefinition($property),
        );

        return true;
    }

    /**
     * Apply legend conditions to its attribute.
     *
     * @param int $attributeId    The attribute setting id.
     * @param int $activeLegendId The legend setting id.
     *
     * @return void
     */
    protected function applyLegendConditions($attributeId, $activeLegendId)
    {
        // No conditions for legend defined.
        if (!isset($this->conditions[$activeLegendId])) {
            return;
        }

        if (!isset($this->conditions[$attributeId])) {
            $this->conditions[$attributeId] = new PropertyConditionChain();
        }

        $this->conditions[$attributeId]->addCondition($this->conditions[$activeLegendId]);
    }

    /**
     * Translate database rows into legend and property information.
     *
     * @param array $rows The database rows.
     *
     * @return void
     *
     * @throws \RuntimeException When an unknown palette rendering mode is encountered
     *                           (neither 'legend' nor 'attribute').
     */
    protected function translateRows($rows)
    {
        $metaModel      = $this->getMetaModel();
        $activeLegend   = $this->translateLegend(
            array('legendtitle' => $metaModel->getName(), 'legendhide' => false),
            $metaModel
        );
        $activeLegendId = null;

        // First pass, fetch all attribute names.
        $columnNames = array();
        foreach ($rows as $row) {
            if ($row['dcatype'] != 'attribute') {
                continue;
            }

            $attribute = $metaModel->getAttributeById($row['attr_id']);
            if ($attribute) {
                $columnNames[$row['id']] = $attribute->getColName();
            }
        }

        $this->propertyMap  = $columnNames;
        $this->propertyMap2 = array_flip($columnNames);

        // Second pass, translate all information into local properties.
        foreach ($rows as $row) {
            switch ($row['dcatype']) {
                case 'legend':
                    $activeLegend   = $this->translateLegend($row, $metaModel);
                    $activeLegendId = $row['id'];
                    break;

                case 'attribute':
                    $exists = $this->translateProperty($row, $metaModel, $activeLegend);

                    if ($exists && $activeLegendId) {
                        $this->applyLegendConditions($row['id'], $activeLegendId);
                    }

                    break;

                default:
                    throw new \RuntimeException('Unknown palette rendering mode ' . $row['dcatype']);
            }
        }
    }

    /**
     * Transform a single condition into a valid condition object.
     *
     * @param array $condition The condition to transform.
     *
     * @return PropertyConditionInterface
     *
     * @throws \RuntimeException When a condition has not been transformed into a valid handling instance.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function transformCondition($condition)
    {
        $dispatcher = $GLOBALS['container']['event-dispatcher'];
        $event      = new CreatePropertyConditionEvent($condition, $this->getMetaModel());

        /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
        $dispatcher->dispatch(CreatePropertyConditionEvent::NAME, $event);

        if ($event->getInstance() === null) {
            throw new \RuntimeException(sprintf(
                'Condition of type %s could not be transformed to an instance.',
                $condition['type']
            ));
        }

        return $event->getInstance();
    }

    /**
     * Transform the given condition array into real conditions.
     *
     * @param array $conditions The property condition information.
     *
     * @return void
     */
    protected function transformConditions($conditions)
    {
        // First pass, sort them into pid.
        $sorted = array();
        $byPid  = array();
        foreach ($conditions as $i => $condition) {
            $sorted[$condition['id']]   = $conditions[$i];
            $byPid[$condition['pid']][] = $condition['id'];
        }

        $instances = array();
        // Second pass, handle them.
        foreach ($sorted as $id => $condition) {
            $instances[$id] = $this->transformCondition($condition);
        }

        // Sort all conditions into their parents.
        foreach ($byPid as $pid => $ids) {
            foreach ($ids as $id) {
                $settingId = $sorted[$id]['settingId'];
                if (!isset($this->conditions[$settingId])) {
                    $this->conditions[$settingId] = new PropertyConditionChain();
                }
                $result    = $this->conditions[$settingId];
                $condition = $instances[$id];
                $parent    = ($pid == 0) ? $result : $instances[$pid];

                // have other classes in the future.
                if ($parent instanceof ConditionChainInterface) {
                    $parent->addCondition($condition);
                } elseif ($parent instanceof NotCondition) {
                    $parent->setCondition($condition);
                }
            }
        }
    }

    /**
     * Transform the grouping and sorting modes.
     *
     * @param array $rows The rows from the Database to convert.
     *
     * @return void
     */
    protected function transformGroupSort($rows)
    {
        foreach ($rows as $row) {
            $this->groupSort[] = new InputScreenGroupingAndSorting($row, $this);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getId()
    {
        return $this->data['id'];
    }

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

    /**
     * {@inheritDoc}
     */
    public function getLegendNames()
    {
        return array_keys($this->legends);
    }

    /**
     * {@inheritDoc}
     */
    public function getLegend($name)
    {
        return $this->legends[$name];
    }

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

    /**
     * {@inheritDoc}
     */
    public function getProperty($name)
    {
        return isset($this->properties[$name]) ? $this->properties[$name] : null;
    }

    /**
     * {@inheritDoc}
     */
    public function getPropertyNames()
    {
        $result = array();
        foreach ($this->getLegends() as $legend) {
            $result = array_merge($result, $legend['properties']);
        }

        return $result;
    }

    /**
     * {@inheritDoc}
     */
    public function getConditionsFor($name)
    {
        $property = $this->propertyMap2[$name];
        return isset($this->conditions[$property]) ? $this->conditions[$property] : null;
    }

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

    /**
     * {@inheritDoc}
     *
     * @throws \RuntimeException When the instance could not be retrieved.
     */
    public function getMetaModel()
    {
        if (null === $this->data) {
            throw new \RuntimeException(
                'No input screen data available, did you forget to define the view combinations?'
            );
        }

        $factory   = $this->container->getFactory();
        $metaModel = $factory->getMetaModel($factory->translateIdToMetaModelName($this->data['pid']));

        if ($metaModel === null) {
            throw new \RuntimeException('Could not retrieve MetaModel with id ' . $this->data['pid']);
        }

        return $metaModel;
    }

    /**
     * {@inheritDoc}
     */
    public function getIcon()
    {
        // Determine image to use.
        if ($this->data['backendicon']) {
            return $this->data['backendicon'];
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public function getBackendSection()
    {
        return trim($this->data['backendsection']);
    }

    /**
     * {@inheritDoc}
     */
    public function getBackendCaption()
    {
        return StringUtil::deserialize($this->data['backendcaption'], true);
    }

    /**
     * {@inheritDoc}
     */
    public function getParentTable()
    {
        return $this->data['ptable'];
    }

    /**
     * {@inheritDoc}
     */
    public function isStandalone()
    {
        return $this->data['rendertype'] == 'standalone';
    }

    /**
     * {@inheritDoc}
     */
    public function getRenderMode()
    {
        // If we have variant overwrite all modes and set mode 5 - tree mode.
        $objMetaModels = $this->getMetaModel();
        if ($objMetaModels->hasVariants()) {
            return 'hierarchical';
        }

        return $this->data['rendermode'];
    }

    /**
     * {@inheritDoc}
     */
    public function isHierarchical()
    {
        return $this->getRenderMode() === 'hierarchical';
    }

    /**
     * {@inheritDoc}
     */
    public function isParented()
    {
        return $this->getRenderMode() === 'parented';
    }

    /**
     * {@inheritDoc}
     */
    public function isFlat()
    {
        return $this->getRenderMode() === 'flat';
    }

    /**
     * {@inheritDoc}
     */
    public function isEditable()
    {
        return (bool) $this->data['iseditable'];
    }

    /**
     * {@inheritDoc}
     */
    public function isCreatable()
    {
        return (bool) $this->data['iscreatable'];
    }

    /**
     * {@inheritDoc}
     */
    public function isDeletable()
    {
        return (bool) $this->data['isdeleteable'];
    }

    /**
     * {@inheritDoc}
     */
    public function getPanelLayout()
    {
        return $this->data['panelLayout'];
    }

    /**
     * {@inheritDoc}
     */
    public function isShowColumns()
    {
        return (bool) $this->data['showColumns'];
    }
}


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\EnvironmentPopulator;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DataProviderDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use Doctrine\DBAL\Connection;
use MetaModels\DcGeneral\Data\Driver;
use MetaModels\IFactory;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * This class handles the populating of the Environments.
 */
class DataProviderPopulator
{
    use MetaModelPopulatorTrait;

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

    /**
     * The event dispatcher to pass to drivers.
     *
     * @var EventDispatcherInterface
     */
    private $dispatcher = null;

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

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

    /**
     * Populate the environment.
     *
     * @param EnvironmentInterface $environment The environment.
     *
     * @return void
     */
    public function populate(EnvironmentInterface $environment)
    {
        foreach ([
            $environment->getDataDefinition(),
            $environment->getParentDataDefinition(),
            $environment->getRootDataDefinition()
        ] as $definition) {
            if (!$definition instanceof ContainerInterface) {
                continue;
            }
            $this->injectServiceContainerIntoDataDrivers($definition->getDataProviderDefinition(), $environment);
        }
    }

    /**
     * Inject the service container into the data driver instances.
     *
     * @param DataProviderDefinitionInterface $providerDefinitions The definitions.
     * @param EnvironmentInterface            $environment         The environment containing the providers.
     *
     * @return void
     */
    private function injectServiceContainerIntoDataDrivers($providerDefinitions, $environment)
    {
        foreach ($providerDefinitions as $provider) {
            $providerInstance = $environment->getDataProvider($provider->getName());
            if ($providerInstance instanceof Driver) {
                $initialization = $provider->getInitializationData();
                $metaModel      = $this->factory->getMetaModel($initialization['source']);
                $providerInstance->setBaseConfig(
                    array_merge($initialization, ['metaModel' => $metaModel])
                );
                $providerInstance->setDispatcher($this->dispatcher);
                $providerInstance->setConnection($this->connection);
            }
        }
    }
}


<?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\Controller\Backend;

use Contao\CoreBundle\Framework\Adapter;
use Contao\System;
use Doctrine\DBAL\Connection;
use MetaModels\Attribute\IAttribute;
use MetaModels\BackendIntegration\PurgeCache;
use MetaModels\IFactory;
use MetaModels\IMetaModel;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * This controller provides the base for the add-all handlers for input screens and render settings.
 */
abstract class AbstractAddAllController
{
    /**
     * Adapter to the Contao\System class.
     *
     * @var System
     */
    private $systemAdapter;

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

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

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

    /**
     * The cache purger.
     *
     * @var PurgeCache
     */
    private $purger;

    /**
     * The list of known attributes.
     *
     * @var array
     */
    private $knownAttributes;

    /**
     * The twig engine.
     *
     * @var EngineInterface
     */
    protected $templating;

    /**
     * Sorting start value.
     *
     * @var int
     */
    private $startSort;

    /**
     * Create a new instance.
     *
     * @param EngineInterface     $templating    The templating instance.
     * @param TranslatorInterface $translator    The translator.
     * @param IFactory            $factory       The MetaModels factory.
     * @param Connection          $connection    The database connection.
     * @param Adapter             $systemAdapter Adapter to the Contao\System class.
     * @param PurgeCache          $purger        The cache purger.
     */
    public function __construct(
        EngineInterface $templating,
        TranslatorInterface $translator,
        IFactory $factory,
        Connection $connection,
        Adapter $systemAdapter,
        PurgeCache $purger
    ) {
        $this->templating    = $templating;
        $this->translator    = $translator;
        $this->factory       = $factory;
        $this->connection    = $connection;
        $this->systemAdapter = $systemAdapter;
        $this->purger        = $purger;
    }

    /**
     * Create an empty data set for inclusion into the database.
     *
     * @param IAttribute $attribute The attribute to generate the data for.
     * @param string     $parentId  The parent id.
     * @param bool       $activate  Flag if the setting shall get activated.
     * @param int        $sort      The sorting value.
     *
     * @return array
     */
    abstract protected function createEmptyDataFor(IAttribute $attribute, $parentId, $activate, $sort);

    /**
     * Test if the passed attribute is acceptable.
     *
     * @param IAttribute $attribute The attribute to check.
     *
     * @return bool
     */
    abstract protected function accepts(IAttribute $attribute);

    /**
     * Process the request.
     *
     * @param string  $table         The table name.
     * @param string  $metaModelName The MetaModel name.
     * @param string  $parentId      The parent id.
     * @param Request $request       The request.
     *
     * @return Response
     *
     * @throws \RuntimeException Throws if could not retrieve a metamodel.
     */
    protected function process($table, $metaModelName, $parentId, Request $request)
    {
        $this->knownAttributes = $this->fetchExisting($table, $parentId);

        $metaModel = $this->factory->getMetaModel($metaModelName);
        if (!$metaModel) {
            throw new \RuntimeException('Could not retrieve MetaModel ' . $metaModelName);
        }
        if ($request->request->has('add') || $request->request->has('saveNclose')) {
            $this->perform($table, $request, $metaModel, $parentId);
            // If we want to close, go back to referer.
            if ($request->request->has('saveNclose')) {
                return new RedirectResponse($this->getReferer($request, $table, false));
            }
        }

        return new Response($this->templating->render(
            'MetaModelsCoreBundle::Backend/add-all.html.twig',
            $this->render($table, $metaModel, $request)
        ));
    }

    /**
     * Render the output array for the template.
     *
     * @param string     $table     The name of the table to add to.
     * @param IMetaModel $metaModel The MetaModel name on which to work on.
     * @param Request    $request   The request.
     *
     * @return array
     */
    protected function render($table, $metaModel, Request $request)
    {
        $fields = $this->generateForm($table, $metaModel, $request);

        return [
            'action'        => '',
            'requestToken'  => REQUEST_TOKEN,
            'href'          => $this->getReferer($request, $table, true),
            'backBt'        => $this->translator->trans('MSC.backBT', [], 'contao_default'),
            'add'           => $this->translator->trans('MSC.continue', [], 'contao_default'),
            'saveNclose'    => $this->translator->trans('MSC.saveNclose', [], 'contao_default'),
            'activate'      => $this->translator->trans($table . '.addAll_activate', [], 'contao_' . $table),
            'headline'      => $this->translator->trans($table . '.addall.1', [], 'contao_' . $table),
            'selectAll'     => $this->translator->trans('MSC.selectAll', [], 'contao_default'),
            'cacheMessage'  => '',
            'updateMessage' => '',
            'hasCheckbox'   => count($fields) > 0,
            'fields'        => $fields,
        ];
    }

    /**
     * Fetch existing entries.
     *
     * @param string $table    The table name to fetch from.
     * @param string $parentId The parent id.
     *
     * @return array
     */
    private function fetchExisting($table, $parentId)
    {
        // Keep the sorting value.
        $this->startSort       = 0;
        $this->knownAttributes = [];

        $alreadyExisting = $this
            ->connection
            ->createQueryBuilder()
            ->select('*')
            ->from($table)
            ->where('pid=:pid')
            ->setParameter('pid', $parentId)
            ->orderBy('sorting')
            ->execute();

        foreach ($alreadyExisting->fetchAll(\PDO::FETCH_ASSOC) as $item) {
            $this->knownAttributes[$item['attr_id']] = $item;
            $this->startSort                         = $item['sorting'];
        }

        return $this->knownAttributes;
    }

    /**
     * Check if an attribute is already present.
     *
     * @param IAttribute $attribute The attribute to check.
     *
     * @return bool
     */
    private function knowsAttribute($attribute)
    {
        return array_key_exists($attribute->get('id'), $this->knownAttributes);
    }

    /**
     * Generate the form.
     *
     * @param string     $table     The table name.
     * @param IMetaModel $metaModel The MetaModel name.
     * @param Request    $request   The request.
     *
     * @return array
     */
    private function generateForm($table, $metaModel, Request $request)
    {
        $fields = [];
        // Loop over all attributes now.
        foreach ($metaModel->getAttributes() as $attribute) {
            $attrId = $attribute->get('id');
            if (!$this->accepts($attribute)) {
                continue;
            }
            if ($this->knowsAttribute($attribute)) {
                $fields[] = [
                    'checkbox' => false,
                    'text'     => $this->checkboxCaption('addAll_alreadycontained', $table, $attribute),
                    'class'    => 'tl_info',
                    'attr_id'  => $attrId,
                    'name'     => 'attribute_' . $attrId
                ];
                continue;
            } elseif ($this->isAttributeSubmitted($attrId, $request)) {
                $fields[] = array(
                    'checkbox' => false,
                    'text'     => $this->checkboxCaption('addAll_addsuccess', $table, $attribute),
                    'class'    => 'tl_confirm',
                    'attr_id'  => $attrId,
                    'name'     => 'attribute_' . $attrId
                );
                continue;
            }
            $fields[] = [
                'checkbox' => true,
                'text'     => $this->checkboxCaption('addAll_willadd', $table, $attribute),
                'class'    => 'tl_new',
                'attr_id'  => $attrId,
                'name'     => 'attribute_' . $attrId
            ];
        }

        return $fields;
    }

    /**
     * Translate the checkbox caption.
     *
     * @param string     $key       The language sub key.
     * @param string     $table     The table name.
     * @param IAttribute $attribute The attribute.
     *
     * @return string
     */
    private function checkboxCaption($key, $table, IAttribute $attribute)
    {
        return $this->translator->trans($table . '.' . $key, [$attribute->getName()], 'contao_' . $table);
    }

    /**
     * Test if an attribute has been submitted.
     *
     * @param string  $attributeId The attribute id.
     * @param Request $request     The request.
     *
     * @return bool
     */
    private function isAttributeSubmitted($attributeId, Request $request)
    {
        return $request->request->has('attribute_' . $attributeId);
    }

    /**
     * Perform addition now.
     *
     * @param string     $table     The table.
     * @param Request    $request   The request.
     * @param IMetaModel $metaModel The MetaModel.
     * @param string     $parentId  The parent id.
     *
     * @return void
     */
    private function perform($table, Request $request, $metaModel, $parentId)
    {
        $activate = (bool) $request->request->get('activate');

        $query = $this
            ->connection
            ->createQueryBuilder()
            ->insert($table);
        foreach ($metaModel->getAttributes() as $attribute) {
            if ($this->knowsAttribute($attribute)
                || !($this->accepts($attribute) && $this->isAttributeSubmitted($attribute->get('id'), $request))
            ) {
                continue;
            }
            $data = [];
            foreach ($this->createEmptyDataFor($attribute, $parentId, $activate, $this->startSort) as $key => $value) {
                $data[$key] = ':' . $key;
                $query->setParameter($key, $value);
            }
            $query->values($data)->execute();
            $this->startSort += 128;
        }
        $this->purger->purge();
    }

    /**
     * Get the current Backend referrer URL.
     *
     * @param Request $request   The request.
     * @param string  $table     The table name.
     * @param bool    $encodeAmp Flag to encode ampersands or not.
     *
     * @return string
     */
    private function getReferer(Request $request, $table, $encodeAmp = false)
    {
        $uri = $this->systemAdapter->getReferer($encodeAmp, $table);
        // Make the location an absolute URL
        if (!preg_match('@^https?://@i', $uri)) {
            $uri = $request->getBasePath() . '/' . ltrim($uri, '/');
        }

        return $uri;
    }
}


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

use Contao\CoreBundle\Monolog\ContaoContext;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Filesystem\Filesystem;

/**
 * Purge the MetaModels cache.
 */
class PurgeCache
{
    /**
     * The cache directory.
     *
     * @var string
     */
    private $cacheDir;

    /**
     * The logger to use.
     *
     * @var LoggerInterface
     */
    private $logger;

    /**
     * Create a new instance.
     *
     * @param string          $cacheDir The cache directory.
     * @param LoggerInterface $logger   The logger.
     */
    public function __construct($cacheDir, LoggerInterface $logger)
    {
        $this->cacheDir = $cacheDir;
        $this->logger   = $logger;
    }

    /**
     * Purge the file cache.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function purge()
    {
        $fileSystem = new Filesystem();
        $fileSystem->remove($this->cacheDir);

        $this->logger->log(
            LogLevel::INFO,
            'Purged the MetaModels cache',
            ['contao' => new ContaoContext(__METHOD__, TL_CRON)]
        );
    }
}


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

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;

/**
 * This provides the render mode options.
 */
class RenderModeOptionListener
{
    /**
     * Retrieve a list of all backend sections, like "content", "system" etc.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function handle(GetPropertyOptionsEvent $event)
    {
        if (('tl_metamodel_dca' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('rendermode' !== $event->getPropertyName())) {
            return;
        }

        $translator = $event->getEnvironment()->getTranslator();
        $options    = [
            'flat'         => $translator->translate('rendermodes.flat', 'tl_metamodel_dca'),
            'hierarchical' => $translator->translate('rendermodes.hierarchical', 'tl_metamodel_dca'),
        ];

        if ('ctable' === $event->getModel()->getProperty('rendertype')) {
            $options['parented'] = $translator->translate('rendermodes.parented', 'tl_metamodel_dca');
        }

        $event->setOptions($options);
    }
}


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

/**
 * This represents a filter URL.
 */
class FilterUrl
{
    /**
     * The page array.
     *
     * @var string[]
     */
    private $page = [];

    /**
     * All parameters to be used as GET parameters.
     *
     * @var string[]
     */
    private $getParameters = [];

    /**
     * All parameters to be used as slug.
     *
     * @var string[]
     */
    private $slugParameters = [];

    /**
     * Create a new instance.
     *
     * @param string[] $page           The page.
     * @param string[] $getParameters  The get parameters.
     * @param string[] $slugParameters The slug parameters.
     */
    public function __construct(
        array $page = [],
        array $getParameters = [],
        array $slugParameters = []
    ) {
        $this->setPage($page);
        foreach ($getParameters as $name => $value) {
            $this->setGet($name, $value);
        }
        foreach ($slugParameters as $name => $value) {
            $this->setSlug($name, $value);
        }
    }

    /**
     * Create a clone of this instance.
     *
     * @return self
     */
    public function clone(): self
    {
        return new FilterUrl(
            $this->getPage(),
            $this->getGetParameters(),
            $this->getSlugParameters()
        );
    }

    /**
     * Set the target page.
     *
     * @param array $page The page.
     *
     * @return self
     */
    public function setPage(array $page): self
    {
        $this->page = $page;

        return $this;
    }

    /**
     * Obtain the target page.
     *
     * @return array
     */
    public function getPage(): array
    {
        return $this->page;
    }

    /**
     * Set a value in the target page.
     *
     * @param string $name  The parameter name.
     * @param mixed  $value The parameter value.
     *
     * @return self
     */
    public function setPageValue(string $name, $value): self
    {
        if (empty($value)) {
            unset($this->page[$name]);
            return $this;
        }

        $this->page[$name] = $value;

        return $this;
    }

    /**
     * Obtain a value from the target page.
     *
     * @param string $name The parameter name.
     *
     * @return mixed
     */
    public function getPageValue(string $name)
    {
        return ($this->page[$name] ?? null);
    }

    /**
     * Add a slug parameter.
     *
     * @param string $name  The slug name.
     * @param string $value The slug value.
     *
     * @return self
     */
    public function setGet(string $name, string $value): self
    {
        if (empty($value)) {
            unset($this->getParameters[$name]);
            return $this;
        }

        $this->getParameters[$name] = $value;

        return $this;
    }

    /**
     * Get a slug parameter.
     *
     * @param string $name The slug name.
     *
     * @return string
     */
    public function getGet(string $name): ?string
    {
        return ($this->getParameters[$name] ?? null);
    }

    /**
     * Test if a slug parameter exists.
     *
     * @param string $name The slug name.
     *
     * @return bool
     */
    public function hasGet(string $name): bool
    {
        return array_key_exists($name, $this->getParameters);
    }

    /**
     * Obtain the slug parameters.
     *
     * @return array
     */
    public function getGetParameters(): array
    {
        return $this->getParameters;
    }

    /**
     * Add a slug parameter.
     *
     * @param string $name  The slug name.
     * @param string $value The slug value.
     *
     * @return self
     */
    public function setSlug(string $name, string $value): self
    {
        if (empty($value)) {
            unset($this->slugParameters[$name]);
            return $this;
        }
        $this->slugParameters[$name] = $value;

        return $this;
    }

    /**
     * Get a slug parameter.
     *
     * @param string $name The slug name.
     *
     * @return string
     */
    public function getSlug(string $name): ?string
    {
        return ($this->slugParameters[$name] ?? null);
    }

    /**
     * Test if a slug parameter exists.
     *
     * @param string $name The slug name.
     *
     * @return bool
     */
    public function hasSlug(string $name): bool
    {
        return array_key_exists($name, $this->slugParameters);
    }

    /**
     * Obtain the slug parameters.
     *
     * @return array
     */
    public function getSlugParameters(): array
    {
        return $this->slugParameters;
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use MetaModels\BackendIntegration\TemplateList;

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

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param TemplateList             $templateList      The template list provider.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator, TemplateList $templateList)
    {
        parent::__construct($scopeDeterminator);
        $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() === 'rte'))) {
            return;
        }

        $configs = [];
        // Delete 'be_' prefix from keys.
        foreach ($this->templateList->getTemplatesForBase('be_tiny') as $key => $value) {
            $configs[substr($key, 3)] = $value;
        }

        $event->setOptions($configs);
    }
}


<?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     Ondrej Brinkel <Sam256@web.de>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     David Maack <david.maack@arcor.de>
 * @author     Oliver Hoff <oliver@hofff.com>
 * @author     Stefan Heimes <stefan_heimes@hotmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\FrontendIntegration;

use Contao\Input;
use Contao\StringUtil;
use Contao\System;
use MetaModels\ItemList;

/**
 * Implementation of the MetaModel content element.
 *
 * @property \FrontendTemplate $Template
 */
class HybridList extends MetaModelHybrid
{
    /**
     * The name to display in the wildcard.
     *
     * @var string
     */
    protected $wildCardName = '### METAMODEL LIST ###';

    /**
     * Generate the list.
     *
     * @return string
     */
    public function generate()
    {
        // Fallback template.
        if (!empty($this->metamodel_layout)) {
            $this->strTemplate = $this->metamodel_layout;
        }

        return parent::generate();
    }

    /**
     * Retrieve all filter parameters from the input class for the specified filter setting.
     *
     * @param ItemList $objItemRenderer The list renderer instance to be used.
     *
     * @return string[]
     */
    protected function getFilterParameters($objItemRenderer)
    {
        $filterUrlBuilder = System::getContainer()->get('metamodels.filter_url');
        $filterUrl        = $filterUrlBuilder->getCurrentFilterUrl();

        $result = [];
        foreach ($objItemRenderer->getFilterSettings()->getParameters() as $name) {
            if ($filterUrl->hasSlug($name)) {
                $result[$name] = $filterUrl->getSlug($name);
            }
            // DAMN Contao - we have to "mark" the keys in the Input class as used as we get an 404 otherwise.
            Input::get($name);
        }

        return $filterUrl->getSlugParameters();
    }

    /**
     * Compile the content element.
     *
     * @return void
     */
    protected function compile()
    {
        $objItemRenderer = new ItemList();

        $this->Template->searchable = !$this->metamodel_donotindex;

        $sorting   = $this->metamodel_sortby;
        $direction = $this->metamodel_sortby_direction;
        if ($this->metamodel_sort_override) {
            if (\Input::get('orderBy')) {
                $sorting = \Input::get('orderBy');
            }
            if (\Input::get('orderDir')) {
                $direction = \Input::get('orderDir');
            }
        }

        $objItemRenderer
            ->setServiceContainerFallback(
                function () {
                    return $this->getServiceContainer();
                }
            )
            ->setFactory(System::getContainer()->get('metamodels.factory'))
            ->setFilterFactory(System::getContainer()->get('metamodels.filter_setting_factory'))
            ->setRenderSettingFactory(System::getContainer()->get('metamodels.render_setting_factory'))
            ->setEventDispatcher(System::getContainer()->get('event_dispatcher'))
            ->setMetaModel($this->metamodel, $this->metamodel_rendersettings)
            ->setLimit($this->metamodel_use_limit, $this->metamodel_offset, $this->metamodel_limit)
            ->setPageBreak($this->perPage)
            ->setSorting($sorting, $direction)
            ->setFilterSettings($this->metamodel_filtering)
            ->setFilterParameters(
                StringUtil::deserialize($this->metamodel_filterparams, true),
                $this->getFilterParameters($objItemRenderer)
            )
            ->setMetaTags($this->metamodel_meta_title, $this->metamodel_meta_description);

        // Render items with encoded email strings as contao standard.
        $this->Template->items         =
            \StringUtil::encodeEmail($objItemRenderer->render($this->metamodel_noparsing, $this));
        $this->Template->numberOfItems = $objItemRenderer->getItems()->getCount();
        $this->Template->pagination    = $objItemRenderer->getPagination();
    }
}


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

/**
 * This factory creates AND property conditions.
 */
abstract class AbstractPropertyConditionChainFactory 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}
     */
    public function buildCondition(array $configuration, IMetaModel $metaModel)
    {
        $children = [];
        if (!empty($configuration['children'])) {
            foreach ($configuration['children'] as $child) {
                $children[] = $this->convertCondition($child, $metaModel);
            }
        }

        return $this->createCondition($children);
    }

    /**
     * Create a condition with the passed children.
     *
     * @param PropertyConditionInterface[] $children The child conditions.
     *
     * @return PropertyConditionInterface
     */
    abstract protected function createCondition(array $children);

    /**
     * {@inheritDoc}
     */
    public function maxChildren()
    {
        return -1;
    }

    /**
     * Perform conversion of a sub condition.
     *
     * @param array      $configuration The condition to convert.
     * @param IMetaModel $metaModel     The MetaModel instance.
     *
     * @return PropertyConditionInterface
     *
     * @throws \RuntimeException Throws condition of type could not be transformed to an instance.
     */
    private function convertCondition($configuration, IMetaModel $metaModel)
    {
        return $this->factory->createCondition($configuration, $metaModel);
    }
}