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

use Contao\StringUtil;
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\Controller\GenerateFrontendUrlEvent;
use ContaoCommunityAlliance\Contao\Bindings\Events\Controller\GetPageDetailsEvent;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
use Doctrine\DBAL\Connection;
use MetaModels\Attribute\IAttributeFactory;
use MetaModels\Filter\IFilter;
use MetaModels\IFactory;
use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;
use MetaModels\Item;

/**
 * Class SearchablePages.
 */
class SearchablePages
{
    /**
     * A list with all pages found by Contao.
     *
     * @var array
     */
    protected $foundPages = array();

    /**
     * A list with all settings from the database.
     *
     * @var array
     */
    private $configs = array();

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

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

    /**
     * Get all configs.
     *
     * @return array
     *
     * @throws \Doctrine\DBAL\DBALException When a database error occur.
     */
    protected function getConfigs()
    {
        if (!count($this->configs)) {
            // Init the config from database.
            $statement = $this->connection->query('SELECT * FROM tl_metamodel_searchable_pages');

            $this->configs = $statement->fetchAll(\PDO::FETCH_ASSOC);
        }

        return $this->configs;
    }

    /**
     * Retrieve the service container.
     *
     * @return IMetaModelsServiceContainer
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function getServiceContainer()
    {
        return $GLOBALS['container']['metamodels-service-container'];
    }

    /**
     * Get the event Dispatcher.
     *
     * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
     */
    protected function getEventDispatcher()
    {
        return $this->getServiceContainer()->getEventDispatcher();
    }

    /**
     * Get the attribute Factory.
     *
     * @return IAttributeFactory
     */
    protected function getAttributeFactory()
    {
        return $this->getServiceContainer()->getAttributeFactory();
    }

    /**
     * Get the MetaModels Factory.
     *
     * @return IFactory
     */
    protected function getMetaModelsFactory()
    {
        return $this->getServiceContainer()->getFactory();
    }

    /**
     * Get a MetaModels by name or id.
     *
     * @param string|int $identifier  The Name or ID of a MetaModels.
     *
     * @param boolean    $ignoreError If true ignore errors like the MetaModels was not found.
     *
     * @return IMetaModel|null
     *
     * @throws \RuntimeException When the MetaModels is missing.
     */
    protected function getMetaModel($identifier, $ignoreError)
    {
        // Get the factory.
        $factory = $this->getMetaModelsFactory();

        // Id to name.
        if (is_numeric($identifier)) {
            $identifier = $factory->translateIdToMetaModelName($identifier);
        }

        // Create mm, if yowl is true check if we have really a mm .
        $metaModels = $factory->getMetaModel($identifier);

        // If $ignoreError is off and we have no mm throw a new exception.
        if (!$ignoreError && $metaModels == null) {
            throw new \RuntimeException('Could not find the MetaModels with the name ' . $identifier);
        }

        return $metaModels;
    }

    /**
     * Get a filter based on the id.
     *
     * @param mixed $identifier Id of the filter.
     *
     * @return \MetaModels\Filter\Setting\ICollection The filter
     */
    protected function getFilterSettings($identifier)
    {
        $filterFactory = $this->getServiceContainer()->getFilterFactory();

        return $filterFactory->createCollection($identifier);
    }

    /**
     * Get the view for a MetaModels.
     *
     * @param string|int $identifier ID/Name of the MetaModels.
     *
     * @param int        $view       ID of the view.
     *
     * @return \MetaModels\Render\Setting\ICollection
     */
    protected function getView($identifier, $view)
    {
        $metaModels = $this->getMetaModel($identifier, false);

        return $metaModels->getView($view);
    }

    /**
     * Get the language.
     *
     * First check the overwrite language. Then check if the MetaModels is translated and get all languages from it.
     * Use the current language as fallback.
     *
     * @param string     $singleLanguage The language with the overwrite.
     *
     * @param IMetaModel $metaModels     The MetaModels for the check.
     *
     * @return string[] A list with all languages or null.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function getLanguage($singleLanguage, $metaModels)
    {
        if (!empty($singleLanguage)) {
            return array($singleLanguage);
        } elseif ($metaModels->isTranslated() && $metaModels->getAvailableLanguages()) {
            return $metaModels->getAvailableLanguages();
        }

        return array($GLOBALS['TL_LANGUAGE']);
    }

    /**
     * Get the list of jumpTos based on the items.
     *
     * @param IMetaModel                             $metaModels The MetaModels to be used.
     *
     * @param IFilter                                $filter     The filter to be used.
     *
     * @param \MetaModels\Render\Setting\ICollection $view       The view to be used.
     *
     * @param string|null                            $rootPage   The root page id or null if there is no root page.
     *
     * @return array A list of urls for the jumpTos
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function getJumpTosFor($metaModels, $filter, $view, $rootPage = null)
    {
        $entries = array();

        // Get the object.
        $items = $metaModels->findByFilter($filter);

        /** @var Item $item */
        foreach ($items as $item) {
            $jumpTo = $item->buildJumpToLink($view);
            $event  = new GetPageDetailsEvent($jumpTo['page']);
            $this->getEventDispatcher()->dispatch(ContaoEvents::CONTROLLER_GET_PAGE_DETAILS, $event);
            $pageDetails = $event->getPageDetails();

            // If there is a root page check the context or if we have no page continue.
            if ($pageDetails === null || ($rootPage !== null && $pageDetails['rootId'] != $rootPage)) {
                continue;
            }

            // Build the url.
            $url = $this->getBaseUrl(
                $pageDetails,
                $jumpTo['url']
            );

            $entries[] = $url->getUrl();
        }

        return $entries;
    }

    /**
     * Get the base URL.
     *
     * @param string[]    $pageDetails The page details.
     *
     * @param null|string $path        Additional path settings.
     *
     * @param bool        $ignoreSSL   If active the system will ignore the 'rootUseSSL' flag.
     *
     * @return UrlBuilder
     */
    private function getBaseUrl($pageDetails, $path = null, $ignoreSSL = false)
    {
        $url = new UrlBuilder();

        // Set the domain (see contao/core#6421)
        if ($pageDetails['domain']) {
            $url->setHost($pageDetails['domain']);
        } else {
            $url->setHost(\Environment::get('host'));
        }

        if ($pageDetails['rootUseSSL'] && !$ignoreSSL) {
            $url->setScheme('https');
        } else {
            $url->setScheme('http');
        }

        // Make a array for the parts.
        $fullPath   = array();
        $fullPath[] = TL_PATH;

        // Get the path.
        if ($path === null) {
            $event = new GenerateFrontendUrlEvent($pageDetails, null, $pageDetails['language'], true);
            $this->getEventDispatcher()->dispatch(ContaoEvents::CONTROLLER_GENERATE_FRONTEND_URL, $event);
            $fullPath[] = $event->getUrl();
        } else {
            $fullPath[] = $path;
        }

        $url->setPath(implode('/', $fullPath));

        return $url;
    }

    /**
     * Remove all empty detail pages.
     *
     * @param array $jumpTos A list with the jumpTo pages.
     *
     * @return void
     */
    protected function removeEmptyDetailPages($jumpTos)
    {
        // Remove the detail pages.
        foreach ($jumpTos as $jumpTo) {
            // Get the page from the url.
            $event = new GetPageDetailsEvent($jumpTo['value']);
            $this->getEventDispatcher()->dispatch(ContaoEvents::CONTROLLER_GET_PAGE_DETAILS, $event);

            $pageDetails = $event->getPageDetails();

            // Check if we have a page If not go to the next one.
            if ($pageDetails === null) {
                continue;
            }

            // Make a full url from it.
            $baseUrl = $this->getBaseUrl($pageDetails);

            if (($strKey = array_search($baseUrl->getUrl(), $this->foundPages)) !== false) {
                unset($this->foundPages[$strKey]);
            }

            // Make a full url from it without the https.
            $baseUrl = $this->getBaseUrl($pageDetails, null, true);

            if (($strKey = array_search($baseUrl->getUrl(), $this->foundPages)) !== false) {
                unset($this->foundPages[$strKey]);
            }
        }
    }

    /**
     * Set parameters.
     *
     * @param string   $filterId The id of the filter.
     *
     * @param string[] $presets  The parameter preset values to use.
     *
     * @param string[] $values   The dynamic parameter values that may be used.
     *
     * @return array
     */
    public function setFilterParameters($filterId, $presets, $values)
    {
        $filterSettings = $this->getFilterSettings($filterId);
        $presetNames    = $filterSettings->getParameters();
        $feFilterParams = array_keys($filterSettings->getParameterFilterNames());
        $processed      = array();

        // We have to use all the preset values we want first.
        foreach ($presets as $strPresetName => $arrPreset) {
            if (in_array($strPresetName, $presetNames)) {
                $processed[$strPresetName] = $arrPreset['value'];
            }
        }

        // Now we have to use all FE filter params, that are either:
        // * not contained within the presets
        // * or are overridable.
        foreach ($feFilterParams as $strParameter) {
            // Unknown parameter? - next please.
            if (!array_key_exists($strParameter, $values)) {
                continue;
            }
            // Not a preset or allowed to override? - use value.
            if ((!array_key_exists($strParameter, $presets)) || $presets[$strParameter]['use_get']) {
                $processed[$strParameter] = $values[$strParameter];
            }
        }

        return $processed;
    }

    /**
     * Start point for the hook getSearchablePages.
     *
     * @param array       $pages       List with all pages.
     *
     * @param int|null    $rootPage    ID of the root page.
     *
     * @param bool|null   $fromSiteMap True when called from sitemap generator, null otherwise.
     *
     * @param string|null $language    The current language.
     *
     * @return array
     *
     * @throws \Doctrine\DBAL\DBALException When an database error occur.
     *
     * @see \RebuildIndex::run()
     * @see \Automator::generateSitemap()
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function addPages($pages, $rootPage = null, $fromSiteMap = false, $language = null)
    {
        // Save the pages.
        $this->foundPages = $pages;
        unset($pages);

        // Run each entry in the published config array.
        foreach ($this->getConfigs() as $config) {
            if (!$config['published']) {
                continue;
            }
            $this->getMetaModelsPages(
                $config,
                $rootPage,
                $language
            );
        }

        asort($this->foundPages);

        // Return the new list.
        return $this->foundPages;
    }

    /**
     * Get a MetaModels, a filter and a renderSetting. Get all items based on the filter and build the jumpTo urls.
     *
     * @param array       $config   ID of the MetaModels.
     *
     * @param string|null $rootPage The root page id or null if there is no root page.
     *
     * @param string|null $language The current language.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getMetaModelsPages(
        $config,
        $rootPage = null,
        $language = null
    ) {
        $metaModelsIdentifier = $config['pid'];
        $filterIdentifier     = $config['filter'];
        $presetParams         = StringUtil::deserialize($config['filterparams'], true);
        $renderSettingId      = $config['rendersetting'];

        // Get the MetaModels.
        $metaModels         = $this->getMetaModel($metaModelsIdentifier, false);
        $availableLanguages = $this->getLanguage($language, $metaModels);
        $currentLanguage    = $GLOBALS['TL_LANGUAGE'];

        foreach ($availableLanguages as $newLanguage) {
            // Change language.
            $GLOBALS['TL_LANGUAGE'] = $newLanguage;

            // Get the view.
            $view    = $this->getView($metaModelsIdentifier, $renderSettingId);
            $jumpTos = $view->get('jumpTo');

            // Set the filter.
            $processed = $this->setFilterParameters($filterIdentifier, $presetParams, array());

            // Create a new filter for the search.
            $filter        = $metaModels->getEmptyFilter();
            $filterSetting = $this->getFilterSettings($filterIdentifier);
            $filterSetting->addRules($filter, $processed);

            // Get all jumpTos.
            $newEntries = $this->getJumpTosFor($metaModels, $filter, $view, $rootPage);

            // Remove all empty page details.
            $this->removeEmptyDetailPages($jumpTos);

            // Reset language.
            $GLOBALS['TL_LANGUAGE'] = $currentLanguage;

            // Merge all results.
            $this->foundPages = array_merge($this->foundPages, $newEntries);
        }

        // Reset the language.
        $GLOBALS['TL_LANGUAGE'] = $currentLanguage;
    }
}


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

use Contao\Database;
use Contao\System;
use Doctrine\DBAL\Connection;
use MetaModels\Filter\Rules\SimpleQuery;

/**
 * Class to generate a MetaModels filter from a data configuration.
 */
class FilterBuilderSql
{
    /**
     * The table name.
     *
     * @var string
     */
    protected $tableName;

    /**
     * The combiner (AND or OR).
     *
     * @var string
     */
    private $combiner;

    /**
     * The SQL procedure.
     *
     * @var string
     */
    private $procedures = array();

    /**
     * The SQL query parameters.
     *
     * @var array
     */
    private $parameter = array();

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

    /**
     * Create a new instance.
     *
     * @param string     $tableName  The table name.
     *
     * @param string     $combiner   The combiner (AND or OR).
     *
     * @param Connection $connection The database connection.
     */
    public function __construct($tableName, $combiner, $connection)
    {
        $this->tableName  = $tableName;
        $this->combiner   = strtoupper($combiner);
        $this->connection = $this->sanitizeConnection($connection);
    }

    /**
     * Check if the builder has any procedure to compile.
     *
     * @return bool
     */
    public function isEmpty()
    {
        return empty($this->procedures);
    }

    /**
     * Build the procedure and return it.
     *
     * @return string
     */
    public function getProcedure()
    {
        return '(' . implode(' ' . $this->combiner . ' ', $this->procedures) . ')';
    }

    /**
     * Retrieve the parameter list.
     *
     * @return array
     */
    public function getParameters()
    {
        return $this->parameter;
    }

    /**
     * Retrieve the filter rule if anything to filter.
     *
     * @return SimpleQuery|null
     */
    public function build()
    {
        if (!$this->isEmpty()) {
            return new SimpleQuery(
                sprintf('SELECT id FROM %s WHERE %s', $this->tableName, $this->getProcedure()),
                $this->getParameters(),
                'id',
                $this->connection
            );
        }

        return null;
    }

    /**
     * Build the sub query for a comparing operator like =,<,>.
     *
     * @param array $operation The operation to apply.
     *
     * @return FilterBuilderSql
     */
    protected function getFilterForComparingOperator($operation)
    {
        $this->parameter[]  = $operation['value'];
        $this->procedures[] = sprintf('(%s %s ?)', $operation['property'], $operation['operation']);

        return $this;
    }

    /**
     * Return the filter query for a "foo IN ('a', 'b')" filter.
     *
     * @param array $operation The operation to apply.
     *
     * @return FilterBuilderSql
     */
    protected function getFilterForInList($operation)
    {
        $this->parameter    = array_merge($this->parameter, array_values($operation['values']));
        $this->procedures[] = sprintf(
            '(%s IN (%s))',
            $operation['property'],
            rtrim(str_repeat('?,', \count($operation['values'])), ',')
        );

        return $this;
    }

    /**
     * Return the filter query for a "foo LIKE '%ba_r%'" filter.
     *
     * The searched value may contain the wildcards '*' and '?' which will get converted to proper SQL.
     *
     * @param array $operation The operation to apply.
     *
     * @return FilterBuilderSql
     */
    protected function getFilterForLike($operation)
    {
        $this->parameter[]  = str_replace(array('*', '?'), array('%', '_'), $operation['value']);
        $this->procedures[] = sprintf('(%s LIKE ?)', $operation['property']);

        return $this;
    }

    /**
     * Add a filter condition child.
     *
     * @param array $child The child.
     *
     * @return FilterBuilderSql
     *
     * @throws \RuntimeException When an invalid filter array has been passed.
     */
    public function addChild($child)
    {
        if (!\is_array($child)) {
            throw new \RuntimeException('Error Processing sub filter: ' . var_export($child, true), 1);
        }

        switch (strtoupper($child['operation'])) {
            case '=':
            case '>':
            case '<':
                return $this->getFilterForComparingOperator($child);

            case 'IN':
                return $this->getFilterForInList($child);

            case 'LIKE':
                return $this->getFilterForLike($child);

            default:
        }

        throw new \RuntimeException('Error processing filter array ' . var_export($child, true), 1);
    }

    /**
     * Add a sub procedure.
     *
     * @param FilterBuilderSql $subProcedure The sub procedure to add.
     *
     * @return FilterBuilderSql
     */
    public function addSubProcedure(FilterBuilderSql $subProcedure)
    {
        $this->procedures[] = $subProcedure->getProcedure();
        $this->parameter    = array_merge($this->parameter, $subProcedure->getParameters());

        return $this;
    }

    /**
     * Sanitize the connection value
     *
     * @param Connection|\Contao\Database $connection The connection value.
     *
     * @return Connection
     *
     * @throws \RuntimeException Throws could not obtain doctrine connection.
     *
     * @deprecated To be removed in 3.0 - you should ALWAYS pass the proper connection.
     */
    private function sanitizeConnection($connection)
    {
        if ($connection instanceof Connection) {
            return $connection;
        }

        // @codingStandardsIgnoreStart
        // @codeCoverageIgnoreStart
        // BC layer - we used to accept a Contao database instance here.
        if ($connection instanceof Database) {
            @trigger_error(
                '"' . __METHOD__ . '" now accepts doctrine instances - ' .
                'passing Contao database instances is deprecated.',
                E_USER_DEPRECATED
            );
            $reflection = new \ReflectionProperty(Database::class, 'resConnection');
            $reflection->setAccessible(true);
            return $reflection->getValue($connection);
        }
        if (null === $connection) {
            @trigger_error(
                'You should pass a doctrine database connection to "' . __METHOD__ . '".',
                E_USER_DEPRECATED
            );
            $connection = System::getContainer()->get('database_connection');
        }

        if (!($connection instanceof Connection)) {
            throw new \RuntimeException('Could not obtain doctrine connection.');
        }

        return $connection;
        // @codeCoverageIgnoreEnd
        // @codingStandardsIgnoreEnd
    }
}


<?php

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

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

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

/**
 * This handles the providing of available templates.
 */
class TemplateOptionListener 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() !== 'template')) {
            return;
        }

        $event->setOptions($this->templateList->getTemplatesForBase('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     Stefan Heimes <stefan_heimes@hotmail.com>
 * @author     David Maack <david.maack@arcor.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\Render\Setting;

use Contao\StringUtil;
use Contao\System;
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\Controller\GetPageDetailsEvent;
use MetaModels\Filter\FilterUrl;
use MetaModels\Filter\FilterUrlBuilder;
use MetaModels\Filter\Setting\IFilterSettingFactory;
use MetaModels\IItem;
use MetaModels\IMetaModel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Base implementation for render settings.
 */
class Collection implements ICollection
{
    /**
     * The MetaModel instance.
     *
     * @var IMetaModel
     */
    protected $metaModel;

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

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

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

    /**
     * The base information for this render settings object.
     *
     * @var array
     */
    protected $arrBase = array();

    /**
     * The sub settings for all attributes.
     *
     * @var array
     */
    protected $arrSettings = array();

    /**
     * The jump to information buffered in this setting.
     *
     * @var array
     */
    protected $jumpToCache;

    /**
     * Create a new instance.
     *
     * @param IMetaModel               $metaModel        The MetaModel instance.
     * @param array                    $arrInformation   The array that holds all base information for the new instance.
     * @param EventDispatcherInterface $dispatcher       The event dispatcher.
     * @param IFilterSettingFactory    $filterFactory    The filter setting factory.
     * @param FilterUrlBuilder         $filterUrlBuilder The filter URL builder.
     */
    public function __construct(
        IMetaModel $metaModel,
        $arrInformation,
        EventDispatcherInterface $dispatcher,
        IFilterSettingFactory $filterFactory,
        FilterUrlBuilder $filterUrlBuilder = null
    ) {
        $this->metaModel     = $metaModel;
        $this->dispatcher    = $dispatcher;
        $this->filterFactory = $filterFactory;
        if (null === $this->dispatcher) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Not passing the event dispatcher as 3rd argument to "' . __METHOD__ . '" is deprecated ' .
                'and will cause an error in MetaModels 3.0',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
        }
        if (null === $this->filterFactory) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Not passing the filter setting factory as 4th argument to "' . __METHOD__ . '" is deprecated ' .
                'and will cause an error in MetaModels 3.0',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
        }
        if (null === $filterUrlBuilder) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Not passing the "FilterUrlBuilder" as 5th argument to "' . __METHOD__ . '" is deprecated ' .
                'and will cause an error in MetaModels 3.0',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
            $filterUrlBuilder = System::getContainer()->get('metamodels.filter_url');
        }
        $this->filterUrlBuilder = $filterUrlBuilder;

        foreach ($arrInformation as $strKey => $varValue) {
            $this->set($strKey, StringUtil::deserialize($varValue));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function get($strName)
    {
        return $this->arrBase[$strName];
    }

    /**
     * {@inheritdoc}
     */
    public function set($strName, $varSetting)
    {
        $this->arrBase[$strName] = $varSetting;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getSetting($strAttributeName)
    {
        return isset($this->arrSettings[$strAttributeName]) ? $this->arrSettings[$strAttributeName] : null;
    }

    /**
     * {@inheritdoc}
     */
    public function setSetting($strAttributeName, $objSetting)
    {
        if ($objSetting) {
            $this->arrSettings[$strAttributeName] = $objSetting->setParent($this);
        } else {
            unset($this->arrSettings[$strAttributeName]);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getSettingNames()
    {
        return array_keys($this->arrSettings);
    }

    /**
     * Retrieve the jump to label.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getJumpToLabel()
    {
        $tableName = $this->metaModel->getTableName();

        if (isset($GLOBALS['TL_LANG']['MSC'][$tableName][$this->get('id')]['details'])) {
            return $GLOBALS['TL_LANG']['MSC'][$tableName][$this->get('id')]['details'];
        } elseif (isset($GLOBALS['TL_LANG']['MSC'][$tableName]['details'])) {
            return $GLOBALS['TL_LANG']['MSC'][$tableName]['details'];
        }

        return $GLOBALS['TL_LANG']['MSC']['details'];
    }

    /**
     * Retrieve the details for the page with the given id.
     *
     * @param string $pageId The id of the page to retrieve the details for.
     *
     * @return array
     */
    private function getPageDetails($pageId)
    {
        if (empty($pageId)) {
            return array();
        }
        $event = new GetPageDetailsEvent($pageId);
        $this->getEventDispatcher()->dispatch(ContaoEvents::CONTROLLER_GET_PAGE_DETAILS, $event);

        return $event->getPageDetails();
    }

    /**
     * Determine the page id and other details.
     *
     * @return array
     */
    private function determineJumpToInformation()
    {
        // Get the right jumpto.
        $translated       = $this->metaModel->isTranslated();
        $desiredLanguage  = $this->metaModel->getActiveLanguage();
        $fallbackLanguage = $this->metaModel->getFallbackLanguage();
        $jumpToPageId     = '';
        $filterSettingId  = '';

        if (!isset($this->jumpToCache[$desiredLanguage . '.' . $fallbackLanguage])) {
            foreach ((array) $this->get('jumpTo') as $jumpTo) {
                $langCode = $jumpTo['langcode'];
                // If either desired language or fallback, keep the result.
                if (!$translated || ($langCode == $desiredLanguage) || ($langCode == $fallbackLanguage)) {
                    $jumpToPageId    = $jumpTo['value'];
                    $filterSettingId = $jumpTo['filter'];
                    // If the desired language, break.
                    // Otherwise try to get the desired one until all have been evaluated.
                    if ($desiredLanguage == $jumpTo['langcode']) {
                        break;
                    }
                }
            }

            $pageDetails   = $this->getPageDetails($jumpToPageId);
            $filterSetting = $filterSettingId
                ? $this->getFilterFactory()->createCollection($filterSettingId)
                : null;

            $this->jumpToCache[$desiredLanguage . '.' . $fallbackLanguage] = array(
                'page'          => $jumpToPageId,
                'pageDetails'   => $pageDetails,
                'filter'        => $filterSettingId,
                'filterSetting' => $filterSetting,
                // Mask out the "all languages" language key (See #687).
                'language'      => $pageDetails['language'],
                'label'         => $this->getJumpToLabel()
            );
        }

        return $this->jumpToCache[$desiredLanguage . '.' . $fallbackLanguage];
    }

    /**
     * {@inheritdoc}
     */
    public function buildJumpToUrlFor(IItem $item)
    {
        $information = $this->determineJumpToInformation();
        if (empty($information['pageDetails'])) {
            return array();
        }

        $result        = $information;
        $parameterList = [];

        $filterUrl = new FilterUrl($information['pageDetails']);
        if (!empty($information['language'])) {
            $filterUrl->setPageValue('language', $information['language']);
        }

        if (!empty($information['filterSetting'])) {
            /** @var \MetaModels\Filter\Setting\ICollection $filterSetting */
            $filterSetting = $information['filterSetting'];
            $parameterList = $filterSetting->generateFilterUrlFrom($item, $this);

            foreach ($parameterList as $strKey => $strValue) {
                // Sadly the filter values are currently encoded due to legacy reasons.
                // For MetaModels 3, they should be passed around decoded everywhere.
                $filterUrl->setSlug($strKey, rawurldecode($strValue));
            }
        }

        $result['params'] = $parameterList;
        $result['deep']   = !empty($filterUrl->getSlugParameters());

        $result['url'] = $this->filterUrlBuilder->generate($filterUrl);

        return $result;
    }

    /**
     * Get the event dispatcher.
     *
     * @return EventDispatcherInterface
     */
    private function getEventDispatcher()
    {
        if ($this->dispatcher) {
            return $this->dispatcher;
        }

        return $this->metaModel->getServiceContainer()->getEventDispatcher();
    }

    /**
     * Get the filter setting factory.
     *
     * @return IFilterSettingFactory
     */
    private function getFilterFactory()
    {
        if ($this->filterFactory) {
            return $this->filterFactory;
        }

        return $this->metaModel->getServiceContainer()->getFilterFactory();
    }
}


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

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use Doctrine\DBAL\Connection;

/**
 * This handles the providing of available filters.
 */
class FilterOptionListener extends AbstractAbstainingListener
{
    /**
     * The connection.
     *
     * @var Connection
     */
    private $connection;

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

    /**
     * Retrieve the options for the attributes.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     */
    public function handle(GetPropertyOptionsEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getPropertyName() !== 'filter')) {
            return;
        }
        $model = $event->getModel();
        $pid   = $model->getProperty('pid');
        if (empty($pid)) {
            return;
        }
        $filters = $this->connection
            ->createQueryBuilder()
            ->select('id', 'name')
            ->from('tl_metamodel_filter')
            ->where('pid=:id')
            ->setParameter('id', $model->getProperty('pid'))
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

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

        $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>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\Assets;

use Contao\CoreBundle\Framework\Adapter;
use Contao\CoreBundle\Image\ImageFactoryInterface;
use Contao\Validator;
use Symfony\Component\Filesystem\Filesystem;

/**
 * This class takes care of building icons for the backend.
 */
class IconBuilder
{
    /**
     * The root path of the application.
     *
     * @var string
     */
    private $rootPath;

    /**
     * The output path for assets.
     *
     * @var string
     */
    private $outputPath;

    /**
     * The web reachable path for assets.
     *
     * @var string
     */
    private $webPath;

    /**
     * Adapter to the Contao\FilesModel class.
     *
     * @var \Contao\FilesModel|Adapter
     */
    private $filesAdapter;

    /**
     * The image factory.
     *
     * @var ImageFactoryInterface
     */
    private $imageFactory;

    /**
     * The image adapter.
     *
     * @var \Contao\Image|Adapter
     */
    private $image;

    /**
     * Create a new instance.
     *
     * @param Adapter               $filesAdapter Adapter to the Contao files model class.
     * @param ImageFactoryInterface $imageFactory The image factory for resizing images.
     * @param string                $rootPath     The root path of the application.
     * @param string                $outputPath   The output path for assets.
     * @param string                $webPath      The web reachable path for assets.
     * @param Adapter               $imageAdapter The image adapter to generate HTML code images.
     */
    public function __construct(
        Adapter $filesAdapter,
        ImageFactoryInterface $imageFactory,
        $rootPath,
        $outputPath,
        $webPath,
        Adapter $imageAdapter
    ) {
        $this->filesAdapter = $filesAdapter;
        $this->imageFactory = $imageFactory;
        $this->rootPath     = $rootPath;
        $this->outputPath   = $outputPath;
        $this->webPath      = $webPath;
        $this->image        = $imageAdapter;

        // Ensure output path exists.
        $fileSystem = new Filesystem();
        $fileSystem->mkdir($this->outputPath);
    }

    /**
     * Get a 16x16 pixel resized icon of the passed image if it exists, return the default icon otherwise.
     *
     * @param string $icon        The icon to resize.
     * @param string $defaultIcon The default icon.
     *
     * @return string
     */
    public function getBackendIcon($icon, $defaultIcon = 'bundles/metamodelscore/images/icons/metamodels.png')
    {
        $realIcon   = $this->convertValueToPath($icon, $defaultIcon);
        $targetPath = $this->outputPath . '/' . basename($realIcon);
        if (\file_exists($targetPath)) {
            return $this->webPath . '/' . basename($realIcon);
        }

        $this->imageFactory->create($realIcon, [16, 16, 'center_center'], $targetPath);

        return $this->webPath . '/' . basename($realIcon);
    }

    /**
     * Get a 16x16 pixel resized icon <img>-tag of the passed image if it exists, return the default MM icon otherwise.
     *
     * @param string $icon        The image path.
     * @param string $alt         An optional alt attribute.
     * @param string $attributes  A string of other attributes.
     * @param string $defaultIcon The default icon.
     *
     * @return string
     */
    public function getBackendIconImageTag(
        $icon,
        $alt = '',
        $attributes = '',
        $defaultIcon = 'bundles/metamodelscore/images/icons/metamodels.png'
    ) {
        return $this->image->getHtml($this->getBackendIcon($icon, $defaultIcon), $alt, $attributes);
    }

    /**
     * Translate the file ID to file path.
     *
     * @param string $varValue The file id.
     * @param string $fallback The fallback file path.
     *
     * @return string
     */
    public function convertValueToPath($varValue, $fallback)
    {
        if (Validator::isUuid($varValue)) {
            $model = $this->filesAdapter->findByPk($varValue);
            if ($model !== null && file_exists($this->rootPath . '/' . $model->path)) {
                return $model->path;
            }
            return $fallback;
        }
        if (file_exists($varValue)) {
            return $varValue;
        }

        return $fallback;
    }
}


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

use Doctrine\DBAL\Connection;
use MetaModels\Filter\Setting\Events\CreateFilterSettingFactoryEvent;
use MetaModels\IFactory;
use MetaModels\IMetaModelsServiceContainer;
use MetaModels\MetaModelsEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * This is the filter settings factory interface.
 */
class FilterSettingFactory implements IFilterSettingFactory
{
    /**
     * The event dispatcher.
     *
     * @var IMetaModelsServiceContainer
     */
    protected $serviceContainer;

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

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

    /**
     * The registered type factories.
     *
     * @var IFilterSettingTypeFactory[]
     */
    private $typeFactories;

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

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

    /**
     * {@inheritdoc}
     *
     * @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::FILTER_SETTING_FACTORY_CREATE)) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Event "' .
                MetaModelsEvents::FILTER_SETTING_FACTORY_CREATE .
                '" is deprecated - register your attribute factories via the service container.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
            $this->eventDispatcher->dispatch(
                MetaModelsEvents::FILTER_SETTING_FACTORY_CREATE,
                new CreateFilterSettingFactoryEvent($this)
            );
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @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;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException When the type is already registered.
     */
    public function addTypeFactory($factory)
    {
        $typeName = $factory->getTypeName();
        if (isset($this->typeFactories[$typeName])) {
            throw new \RuntimeException('Filter type ' . $typeName . ' is already registered.');
        }
        $this->typeFactories[$typeName] = $factory;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getTypeFactory($type)
    {
        return isset($this->typeFactories[$type]) ? $this->typeFactories[$type] : null;
    }

    /**
     * Create a new setting.
     *
     * @param array       $dbResult       The information from which to initialize the setting from.
     *
     * @param ICollection $filterSettings The MetaModel filter settings.
     *
     * @return ISimple|null
     */
    private function createSetting($dbResult, $filterSettings)
    {
        $factory = $this->getTypeFactory($dbResult['type']);
        if ($factory) {
            $setting = $factory->createInstance($dbResult, $filterSettings);

            if (!$setting) {
                return null;
            }

            // Collect next level.
            if ($factory->isNestedType()) {
                /** @var IWithChildren $setting */
                $this->collectRulesFor($setting, $filterSettings);
            }

            return $setting;
        }

        return null;
    }

    /**
     * Fetch all child rules for the given setting.
     *
     * @param IWithChildren $parentSetting  The information from which to initialize the setting from.
     *
     * @param ICollection   $filterSettings The filter setting instance.
     *
     * @return void
     */
    private function collectRulesFor($parentSetting, $filterSettings)
    {
        $childInformation = $this->database
            ->createQueryBuilder()
            ->select('*')
            ->from('tl_metamodel_filtersetting')
            ->where('pid=:pid')
            ->andWhere('enabled=1')
            ->orderBy('sorting', 'ASC')
            ->setParameter('pid', $parentSetting->get('id'))
            ->execute();

        foreach ($childInformation->fetchAll(\PDO::FETCH_ASSOC) as $item) {
            $childSetting = $this->createSetting($item, $filterSettings);
            if ($childSetting) {
                $parentSetting->addChild($childSetting);
            }
        }
    }

    /**
     * Collect the rules for a filter setting.
     *
     * @param Collection $filterSettings The filter settings instance.
     *
     * @return void
     */
    public function collectRules($filterSettings)
    {
        $information = $this->database
            ->createQueryBuilder()
            ->select('*')
            ->from('tl_metamodel_filtersetting')
            ->where('fid=:fid')
            ->andWhere('pid=0')
            ->andWhere('enabled=1')
            ->orderBy('sorting', 'ASC')
            ->setParameter('fid', $filterSettings->get('id'))
            ->execute();

        foreach ($information->fetchAll(\PDO::FETCH_ASSOC) as $item) {
            $newSetting = $this->createSetting($item, $filterSettings);
            if ($newSetting) {
                $filterSettings->addSetting($newSetting);
            }
        }
    }

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException When the MetaModel could not be retrieved.
     */
    public function createCollection($settingId)
    {
        $query = $this->database
            ->createQueryBuilder()
            ->select('*')
            ->from('tl_metamodel_filter')
            ->where('id=:id')
            ->setMaxResults(1)
            ->setParameter('id', $settingId)
            ->execute();
        if (!$query) {
            throw new \RuntimeException('Could not retrieve filter setting');
        }

        if (!empty($information = $query->fetch(\PDO::FETCH_ASSOC))) {
            $metaModel = $this->factory->getMetaModel($this->factory->translateIdToMetaModelName($information['pid']));
            if ($metaModel === null) {
                throw new \RuntimeException('Could not retrieve MetaModel ' . $information['pid']);
            }
            $collection = new Collection($information);

            $collection->setMetaModel($metaModel);
            $this->collectRules($collection);

            return $collection;
        }

        return new Collection(array());
    }

    /**
     * {@inheritdoc}
     */
    public function getTypeNames()
    {
        return array_keys($this->typeFactories);
    }
}


<?php

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

namespace MetaModels\CoreBundle\Contao\Hooks;

use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilderFactoryInterface;
use Doctrine\DBAL\Connection;
use MetaModels\BackendIntegration\TemplateList;
use MetaModels\CoreBundle\Assets\IconBuilder;
use MetaModels\Filter\Setting\FilterSettingFactory;
use MetaModels\IFactory;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * This class is the abstract base for building the "edit MetaModel" button in the backend.
 */
abstract class AbstractContentElementAndModuleCallback
{
    /**
     * The table name.
     *
     * @var string
     */
    protected static $tableName;

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

    /**
     * The URL builder factory.
     *
     * @var UrlBuilderFactoryInterface
     */
    private $urlBuilderFactory;

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

    /**
     * The filtersetting factory.
     *
     * @var FilterSettingFactory
     */
    private $filterFactory;

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

    /**
     * The template list.
     *
     * @var TemplateList
     */
    private $templateList;

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

    /**
     * Create a new instance.
     *
     * @param IconBuilder                $iconBuilder       The icon builder.
     * @param UrlBuilderFactoryInterface $urlBuilderFactory The URL builder.
     * @param IFactory                   $factory           The MetaModel factory.
     * @param FilterSettingFactory       $filterFactory     The filter factory.
     * @param Connection                 $connection        The database connection.
     * @param TemplateList               $templateList      The template list loader.
     * @param RequestStack               $requestStack      The request stack.
     */
    public function __construct(
        IconBuilder $iconBuilder,
        UrlBuilderFactoryInterface $urlBuilderFactory,
        IFactory $factory,
        FilterSettingFactory $filterFactory,
        Connection $connection,
        TemplateList $templateList,
        RequestStack $requestStack
    ) {
        $this->iconBuilder       = $iconBuilder;
        $this->urlBuilderFactory = $urlBuilderFactory;
        $this->filterFactory     = $filterFactory;
        $this->connection        = $connection;
        $this->templateList      = $templateList;
        $this->factory           = $factory;
        $this->requestStack      = $requestStack;
    }

    /**
     * Return the edit wizard.
     *
     * @param \DC_Table $dataContainer The data container.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function editMetaModelButton(\DC_Table $dataContainer)
    {
        if ($dataContainer->value < 1) {
            return '';
        }

        $url = $this->urlBuilderFactory->create('contao/main.php?do=metamodels&act=edit')
            ->setQueryParameter('id', ModelId::fromValues('tl_metamodel', $dataContainer->value)->getSerialized());

        return $this->renderEditButton(
            $GLOBALS['TL_LANG'][static::$tableName]['editmetamodel'][0],
            sprintf(
                StringUtil::specialchars($GLOBALS['TL_LANG'][static::$tableName]['editmetamodel'][1]),
                $dataContainer->value
            ),
            $url
        );
    }

    /**
     * Return the edit wizard.
     *
     * @param \DC_Table $dataContainer The data container.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function editFilterSettingButton(\DC_Table $dataContainer)
    {
        if ($dataContainer->value < 1) {
            return '';
        }

        $url = $this->urlBuilderFactory->create('contao/main.php?do=metamodels&table=tl_metamodel_filtersetting')
            ->setQueryParameter(
                'pid',
                ModelId::fromValues('tl_metamodel_filter', $dataContainer->value)->getSerialized()
            );

        return $this->renderEditButton(
            $GLOBALS['TL_LANG'][static::$tableName]['editfiltersetting'][0],
            sprintf(
                StringUtil::specialchars($GLOBALS['TL_LANG'][static::$tableName]['editfiltersetting'][1]),
                $dataContainer->value
            ),
            $url
        );
    }

    /**
     * Return the edit wizard.
     *
     * @param \DC_Table $dataContainer The data container.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function editRenderSettingButton(\DC_Table $dataContainer)
    {
        if ($dataContainer->value < 1) {
            return '';
        }

        $url = $this->urlBuilderFactory->create('contao/main.php?do=metamodels&table=tl_metamodel_rendersetting')
            ->setQueryParameter(
                'pid',
                ModelId::fromValues('tl_metamodel_rendersettings', $dataContainer->value)->getSerialized()
            );

        return $this->renderEditButton(
            $GLOBALS['TL_LANG'][static::$tableName]['editrendersetting'][0],
            sprintf(
                StringUtil::specialchars($GLOBALS['TL_LANG'][static::$tableName]['editrendersetting'][1]),
                $dataContainer->value
            ),
            $url
        );
    }

    /**
     * Fetch all attribute names for the current MetaModel.
     *
     * @param \DC_Table $objDc The data container calling this method.
     *
     * @return string[] array of all attributes as colName => human name
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getAttributeNames(\DC_Table $objDc)
    {
        $attributeNames = [
            'sorting' => $GLOBALS['TL_LANG']['MSC']['metamodels_sorting'],
            'random'  => $GLOBALS['TL_LANG']['MSC']['random'],
            'id'      => $GLOBALS['TL_LANG']['MSC']['id'][0]
        ];

        $metaModelName = $this->factory->translateIdToMetaModelName($objDc->activeRecord->metamodel);
        $metaModel     = $this->factory->getMetaModel($metaModelName);

        if ($metaModel) {
            foreach ($metaModel->getAttributes() as $objAttribute) {
                $attributeNames[$objAttribute->getColName()] = $objAttribute->getName();
            }
        }

        return $attributeNames;
    }

    /**
     * Fetch all available filter settings for the current meta model.
     *
     * @param \DC_Table $objDC The data container calling this method.
     *
     * @return string[] array of all attributes as id => human name
     */
    public function getFilterSettings(\DC_Table $objDC)
    {
        $filterSettings = $this->connection->createQueryBuilder()
            ->select('f.id', 'f.name')
            ->from('tl_metamodel_filter', 'f')
            ->where('f.pid=:id')
            ->setParameter('id', $objDC->activeRecord->metamodel)
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

        $result = [];
        foreach ($filterSettings as $filterSetting) {
            $result[$filterSetting['id']] = $filterSetting['name'];
        }

        // Sort the filter settings.
        asort($result);

        return $result;
    }

    /**
     * Get a list with all allowed attributes for meta title.
     *
     * @param \DC_Table $objDC The data container calling this method.
     *
     * @return array A list with all found attributes.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getMetaTitleAttributes(\DC_Table $objDC)
    {
        return $this->getFilteredAttributeNames(
            $objDC->activeRecord->metamodel,
            (array) $GLOBALS['METAMODELS']['metainformation']['allowedTitle']
        );
    }

    /**
     * Get a list with all allowed attributes for meta description.
     *
     * @param \DC_Table $objDC The data container calling this method.
     *
     * @return array A list with all found attributes.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getMetaDescriptionAttributes(\DC_Table $objDC)
    {
        return $this->getFilteredAttributeNames(
            $objDC->activeRecord->metamodel,
            (array) $GLOBALS['METAMODELS']['metainformation']['allowedDescription']
        );
    }

    /**
     * Called from subclass.
     *
     * @param \DC_Table $dataContainer The data container calling this method.
     * @param string    $elementName   The type name to search for.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function buildFilterParamsFor(\DC_Table $dataContainer, $elementName)
    {
        if (!$this->requestStack->getCurrentRequest()->query->get('act')) {
            return;
        }

        $filterId = $this->connection->createQueryBuilder()
            ->select('c.metamodel_filtering')
            ->from(static::$tableName, 'c')
            ->join('c', 'tl_metamodel', 'mm', 'mm.id=c.metamodel')
            ->where('c.id=:id')
            ->setParameter('id', $dataContainer->id)
            ->andWhere('c.type=:type')
            ->setParameter('type', $elementName)
            ->setMaxResults(1)
            ->execute()
            ->fetch(\PDO::FETCH_COLUMN);

        if (!$filterId) {
            unset($GLOBALS['TL_DCA'][static::$tableName]['fields']['metamodel_filterparams']);
            return;
        }

        $collection = $this->filterFactory->createCollection($filterId);
        $dca        = $collection->getParameterDCA();
        foreach ($dca as $fieldName => $subField) {
            $options = [];
            foreach ($subField['options'] as $key => $value) {
                $options[$this->loadCallback($key)] = $value;
            }

            $dca[$fieldName]['options']         = $options;
            $dca[$fieldName]['save_callback'][] = [static::class, 'saveCallback'];
            $dca[$fieldName]['load_callback'][] = [static::class, 'loadCallback'];
        }

        $GLOBALS['TL_DCA'][static::$tableName]['fields']['metamodel_filterparams']['eval']['subfields'] =
            $dca;
    }

    /**
     * Base64 decode.
     *
     * @param string|null $value The value to save.
     *
     * @return string|null
     */
    public function saveCallback(string $value = null)
    {
        return null === $value ? null : \base64_decode($value);
    }

    /**
     * Base64 encode.
     *
     * @param string|null $value The value.
     *
     * @return string|null
     */
    public function loadCallback(string $value = null)
    {
        return null === $value ? null : trim(\base64_encode($value), '=');
    }

    /**
     * Get attributes for checkbox wizard.
     *
     * @param \DC_Table $objDc The current row.
     *
     * @return array
     */
    public function getFilterParameterNames(\DC_Table $objDc)
    {
        $return = array();
        $filter = $objDc->activeRecord->metamodel_filtering;

        if (!$filter) {
            return $return;
        }

        $collection = $this->filterFactory->createCollection($filter);

        return $collection->getParameterFilterNames();
    }

    /**
     * Get frontend templates for filters.
     *
     * @return array
     */
    public function getFilterTemplates()
    {
        return $this->templateList->getTemplatesForBase('mm_filter_');
    }

    /**
     * Fetch the template group for the current MetaModel module.
     *
     * @param string $base The template base.
     *
     * @return array
     */
    protected function getTemplateList($base)
    {
        return $this->templateList->getTemplatesForBase($base);
    }

    /**
     * Fetch all available render settings for the current meta model.
     *
     * @param \DC_Table $objDC The data container calling this method.
     *
     * @return string[] array of all attributes as id => human name
     */
    public function getRenderSettings(\DC_Table $objDC)
    {
        $filterSettings = $this->connection->createQueryBuilder()
            ->select('r.id', 'r.name')
            ->from('tl_metamodel_rendersettings', 'r')
            ->where('r.pid=:id')
            ->setParameter('id', $objDC->activeRecord->metamodel)
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

        $result = [];
        foreach ($filterSettings as $filterSetting) {
            $result[$filterSetting['id']] = $filterSetting['name'];
        }

        // Sort the filter settings.
        asort($result);

        return $result;
    }

    /**
     * Render an edit button.
     *
     * @param string     $caption The caption (alt attribute of the image).
     * @param string     $title   The title (title attribute of the  tag).
     * @param UrlBuilder $url     The URL for the button.
     *
     * @return string
     */
    private function renderEditButton($caption, $title, UrlBuilder $url)
    {
        $icon = $this->iconBuilder->getBackendIconImageTag(
            'system/themes/flexible/icons/alias.svg',
            $caption,
            'style="vertical-align:top"'
        );

        return sprintf(
            '%s',
            $url->getUrl(),
            $title,
            $icon
        );
    }

    /**
     * Get a list with all allowed attributes for meta description.
     *
     * If the optional parameter arrTypes is not given, all attributes will be retrieved.
     *
     * @param int      $metaModelId  The id of the MetaModel from which the attributes shall be retrieved from.
     *
     * @param string[] $allowedTypes The attribute type names that shall be retrieved (optional).
     *
     * @return array A list with all found attributes.
     */
    private function getFilteredAttributeNames($metaModelId, $allowedTypes = array())
    {
        $attributeNames = array();

        if ($metaModel = $this->factory->getMetaModel($this->factory->translateIdToMetaModelName($metaModelId))) {
            foreach ($metaModel->getAttributes() as $attribute) {
                if (empty($allowedTypes) || in_array($attribute->get('type'), $allowedTypes)) {
                    $attributeNames[$attribute->getColName()] =
                        sprintf(
                            '%s [%s]',
                            $attribute->getName(),
                            $attribute->getColName()
                        );
                }
            }
        }

        return $attributeNames;
    }
}


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

/**
 * This trait provides access to MetaModel instances.
 */
trait GetMetaModelTrait
{
    /**
     * The MetaModel factory.
     *
     * @var IFactory
     */
    private $factory;

    /**
     * Retrieve the MetaModel instance.
     *
     * @param string $metaModelId The MetaModel id.
     *
     * @return IMetaModel
     *
     * @throws \RuntimeException Throws if no factory set.
     */
    protected function getMetaModel($metaModelId)
    {
        if (null === $this->factory) {
            throw new \RuntimeException('No factory set.');
        }

        $metaModelName = $this->factory->translateIdToMetaModelName($metaModelId);
        $metaModel     = $this->factory->getMetaModel($metaModelName);

        return $metaModel;
    }

    /**
     * {@inheritDoc}
     */
    public function setMetaModelFactory(IFactory $factory)
    {
        $this->factory = $factory;
    }
}


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

/**
 * This class provides callbacks for tl_module.
 */
class ModuleCallback extends AbstractContentElementAndModuleCallback
{
    /**
     * The table name.
     *
     * @var string
     */
    protected static $tableName = 'tl_module';

    /**
     * Called from tl_content.onload_callback.
     *
     * @param \DC_Table $dataContainer The data container calling this method.
     *
     * @return void
     */
    public function buildFilterParameterList(\DC_Table $dataContainer)
    {
        parent::buildFilterParamsFor($dataContainer, 'metamodel_list');
    }

    /**
     * Fetch the template group for the current MetaModel content element.
     *
     * @param \DC_Table $objDC The data container calling this method.
     *
     * @return array
     */
    public function getTemplates(\DC_Table $objDC)
    {
        /** @noinspection PhpUndefinedFieldInspection */
        $type = $objDC->activeRecord->type;

        return $this->getTemplateList('mod_' . $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     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\Condition;

use MetaModels\Filter\Filter;
use MetaModels\Filter\IFilter;
use MetaModels\Filter\Rules\Condition\ConditionAnd as FilterRuleAnd;
use MetaModels\Filter\Setting\WithChildren;

/**
 * This filter condition generates a "AND" condition from all child filter settings.
 * The generated rule will only return ids that are mentioned in ALL child rules.
 */
class ConditionAnd extends WithChildren
{
    /**
     * {@inheritdoc}
     */
    public function prepareRules(IFilter $objFilter, $arrFilterUrl)
    {
        $objSubFilter = new Filter($this->getMetaModel());

        foreach ($this->arrChildren as $objChildSetting) {
            $objChildSetting->prepareRules($objSubFilter, $arrFilterUrl);
        }

        $objFilterRule = new FilterRuleAnd();
        $objFilterRule->addChild($objSubFilter);

        $objFilter->addFilterRule($objFilterRule);
    }
}


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

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

use Contao\CoreBundle\Picker\PickerBuilderInterface;
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\Image\GenerateHtmlEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminatorAwareTrait;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\ContaoBackendViewTemplate;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ManipulateWidgetEvent;

/**
 * This handles the wizard button for the language code jump to field in the multicolumn wizard.
 */
class LanguageCodeWizardListener
{
    use RequestScopeDeterminatorAwareTrait;

    /**
     * The picker builder.
     *
     * @var PickerBuilderInterface
     */
    private $pickerBuilder;

    /**
     * LanguageCodeWizardListener constructor.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The request scope matcher.
     * @param PickerBuilderInterface   $pickerBuilder     The picker builder.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator, PickerBuilderInterface $pickerBuilder)
    {
        $this->scopeDeterminator = $scopeDeterminator;
        $this->pickerBuilder     = $pickerBuilder;
    }

    /**
     * Return the link picker wizard - this is called from Multi column wizard in render settings jumpTo page handling.
     *
     * @param ManipulateWidgetEvent $event The event.
     *
     * @return void
     */
    public function pagePicker(ManipulateWidgetEvent $event)
    {
        if (!$this->scopeDeterminator->currentScopeIsBackend()
            || !('tl_metamodel_rendersettings' === $event->getEnvironment()->getDataDefinition()->getName())
            || !((0 === strpos($event->getProperty()->getName(), 'jumpTo'))
                 && ('[value]' === substr($event->getProperty()->getName(), -\strlen('[value]'))))
        ) {
            return;
        }

        $environment = $event->getEnvironment();

        $pickerUrl = $this->pickerBuilder->getUrl('cca_link');

        $urlEvent = new GenerateHtmlEvent(
            'pickpage.svg',
            $environment->getTranslator()->translate('MSC.pagepicker'),
            'style="vertical-middle:top;cursor:pointer"'
        );

        $environment->getEventDispatcher()->dispatch(ContaoEvents::IMAGE_GET_HTML, $urlEvent);

        $template = new ContaoBackendViewTemplate('dc_general_wizard_link_url_picker');
        $template
            ->set('name', $event->getWidget()->name)
            ->set('popupUrl', $pickerUrl)
            ->set('html', ' ' . $urlEvent->getHtml())
            ->set('label', $event->getProperty()->getLabel()[1])
            ->set('id', $event->getWidget()->id);

        $event->getWidget()->wizard = $template->parse();
    }
}


<?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>
 * @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 MetaModels\Helper\TableManipulator;
use MetaModels\IMetaModel;

/**
 * Reference implementation for Simple attributes.
 * Simple fields are fields that only consist of one column in the metamodel table and therefore do not need
 * to be handled as complex fields must be.
 *
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class BaseSimple extends Base implements ISimple
{
    /**
     * Database connection.
     *
     * @var Connection
     */
    protected $connection;

    /**
     * Table manipulator.
     *
     * @var TableManipulator
     */
    protected $tableManipulator;

    /**
     * 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       The database connection.
     *
     * @param TableManipulator $tableManipulator Table manipulator instance.
     */
    public function __construct(
        IMetaModel $objMetaModel,
        $arrData = [],
        Connection $connection = null,
        TableManipulator $tableManipulator = 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');
        }

        if (null === $tableManipulator) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Table manipulator is missing. It has to be passed in the constructor. Fallback will be dropped.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd

            $tableManipulator = System::getContainer()->get('metamodels.table_manipulator');
        }

        $this->connection       = $connection;
        $this->tableManipulator = $tableManipulator;
    }

    /**
     * Updates the meta information of the attribute.
     *
     * This tells the attribute to perform any actions that must be done to correctly initialize the new value
     * and to perform any action to undo the changes that had been done for the previous value.
     * i.e.: when an attribute type needs columns in an an auxiliary table, these will have to be updated herein.
     *
     * This method may throw an exception, when the new value is invalid or any problems appear, the MetaModelAttribute
     * will then keep the old meta value.
     *
     * @param string $strMetaName Name of the meta information that shall be updated.
     *
     * @param mixed  $varNewValue The new value for this meta information.
     *
     * @return \MetaModels\Attribute\IAttribute The instance of this attribute, to support chaining.
     */
    public function handleMetaChange($strMetaName, $varNewValue)
    {
        // By default we accept any change of meta information.
        if ($strMetaName == 'colname') {
            if ($this->get($strMetaName) != $varNewValue) {
                $this->renameColumn($varNewValue);
            }
            return $this;
        }
        return parent::handleMetaChange($strMetaName, $varNewValue);
    }

    /**
     * This method is called to store the data for certain items to the database.
     *
     * @param mixed $arrValues The values to be stored into database. Mapping is item id=>value.
     *
     * @return void
     */
    public function setDataFor($arrValues)
    {
        $strTable   = $this->getMetaModel()->getTableName();
        $strColName = $this->getColName();
        foreach ($arrValues as $intId => $varData) {
            if (is_array($varData)) {
                $varData = serialize($varData);
            }

            $this->connection->update($strTable, [$strColName => $varData], ['id' => $intId]);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
    {
        // If empty list, return empty result. See also #379 for discussion.
        if ($idList === array()) {
            return array();
        }

        $strCol = $this->getColName();
        if ($idList) {
            $statement = $this->connection->createQueryBuilder()
                ->select($strCol . ', COUNT(' . $strCol . ') as mm_count')
                ->from($this->getMetaModel()->getTableName())
                ->where('id IN (:ids)')
                ->groupBy($strCol)
                ->orderBy('FIELD(id, :ids)')
                ->setParameter('ids', $idList, Connection::PARAM_STR_ARRAY)
                ->execute();
        } else {
            $statement = $this->connection->createQueryBuilder()
                ->select($strCol . ', COUNT(' . $strCol . ') as mm_count')
                ->from($this->getMetaModel()->getTableName())
                ->groupBy($strCol)
                ->orderBy($strCol)
                ->execute();
        }

        $arrResult = array();
        while ($objRow = $statement->fetch(\PDO::FETCH_OBJ)) {
            if (is_array($arrCount)) {
                $arrCount[$objRow->$strCol] = $objRow->mm_count;
            }

            $arrResult[$objRow->$strCol] = $objRow->$strCol;
        }
        return $arrResult;
    }

    /**
     * {@inheritdoc}
     *
     * This base implementation does a plain SQL sort by native value as defined by MySQL.
     */
    public function sortIds($idList, $strDirection)
    {
        // Base implementation, do a simple sorting on given column.
        $idList = $this->connection->createQueryBuilder()
            ->select('id')
            ->from($this->getMetaModel()->getTableName())
            ->where('id IN (:ids)')
            ->setParameter('ids', $idList, Connection::PARAM_STR_ARRAY)
            ->orderBy($this->getColName(), $strDirection)
            ->execute()
            ->fetchAll(\PDO::FETCH_COLUMN, 'id');

        return $idList;
    }

    /**
     * Search all items that match the given expression.
     *
     * Base implementation, perform string matching search.
     * The standard wildcards * (many characters) and ? (a single character) are supported.
     *
     * @param string $strPattern The text to search for. This may contain wildcards.
     *
     * @return int[] the ids of matching items.
     */
    public function searchFor($strPattern)
    {
        // Base implementation, do a simple search on given column.
        $strPattern = str_replace(array('*', '?'), array('%', '_'), $strPattern);
        $arrIds     = $this->connection->createQueryBuilder()
            ->select('id')
            ->from($this->getMetaModel()->getTableName())
            ->where($this->getColName() . ' LIKE :pattern')
            ->setParameter('pattern', $strPattern)
            ->execute()
            ->fetchAll(\PDO::FETCH_COLUMN, 'id');

        return $arrIds;
    }

    /**
     * Returns the SQL primitive type declaration in MySQL notation.
     *
     * In this base class a sane value of "blob" allowing NULL is used.
     * Deriving classes SHOULD override this function.
     *
     * @return string 'blob NULL'
     */
    public function getSQLDataType()
    {
        return 'blob NULL';
    }

    /**
     * Create auxiliary data like a column in the MetaModel table or references in another table etc.
     *
     * @return void
     */
    public function destroyAUX()
    {
        parent::destroyAUX();
        $this->deleteColumn();
    }

    /**
     * Delete all auxiliary data like a column in the MetaModel table or references in another table etc.
     *
     * @return void
     */
    public function initializeAUX()
    {
        parent::initializeAUX();
        $this->createColumn();
    }

    /**
     * Creates the underlying database structure for this field.
     *
     * You have to override this function in field types, when you want to have multi column structure etc.
     *
     * @return void
     */
    public function createColumn()
    {
        if ($this->getColName()) {
            $this->tableManipulator->createColumn(
                $this->getMetaModel()->getTableName(),
                $this->getColName(),
                $this->getSQLDataType()
            );
        }
    }

    /**
     * Removes the underlying database structure for this field.
     *
     * @return void
     */
    public function deleteColumn()
    {
        $schemaManager = $this->connection->getSchemaManager();
        $columns       = $schemaManager->listTableColumns($this->getMetaModel()->getTableName());

        // Try to delete the column. If it does not exist as we can assume it has been deleted already then.
        if ($this->getColName() && isset($columns[$this->getColName()])) {
            $this->tableManipulator->dropColumn($this->getMetaModel()->getTableName(), $this->getColName());
        }
    }

    /**
     * Renames the underlying database structure for this field.
     *
     * @param string $strNewColumnName The new column name.
     *
     * @return void
     */
    public function renameColumn($strNewColumnName)
    {
        $this->tableManipulator->checkColumnName($strNewColumnName);

        $schemaManager = $this->connection->getSchemaManager();
        $columns       = $schemaManager->listTableIndexes($this->getMetaModel()->getTableName());

        if ($this->getColName() && isset($columns[$this->getColName()])) {
            $this->tableManipulator->renameColumn(
                $this->getMetaModel()->getTableName(),
                $this->getColName(),
                $strNewColumnName,
                $this->getSQLDataType()
            );
        } else {
            $strBackupColName = $this->getColName();
            $this->set('colname', $strNewColumnName);
            $this->createColumn();
            $this->set('colname', $strBackupColName);
        }
    }

    /**
     * Take the raw data from the DB column and unserialize it.
     *
     * @param string $value The input value.
     *
     * @return mixed
     */
    public function unserializeData($value)
    {
        return $value;
    }

    /**
     * Take the unserialized data and serialize it for the native DB column.
     *
     * @param mixed $value The input value.
     *
     * @return string
     */
    public function serializeData($value)
    {
        return $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>
 * @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 Doctrine\DBAL\Connection;
use MenAtWork\MultiColumnWizardBundle\Event\GetOptionsEvent;

/**
 * This class handles obtaining the input screen options.
 */
class InputScreenOptionListener
{
    /**
     * The database connection.
     *
     * @var Connection
     */
    private $connection;

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

    /**
     * Get all options for the input screens.
     *
     * @param GetOptionsEvent $event The event.
     *
     * @return void
     */
    public function handle(GetOptionsEvent $event)
    {
        if (('tl_metamodel_dca_combine' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('rows' !== $event->getPropertyName())
            || ('dca_id' !== $event->getSubPropertyName())) {
            return;
        }

        $screens = $this
            ->connection
            ->createQueryBuilder()
            ->select('id')
            ->addSelect('name')
            ->from('tl_metamodel_dca')
            ->where('pid=:pid')
            ->setParameter('pid', $event->getModel()->getProperty('id'))
            ->execute()
            ->fetchAll(\PDO::FETCH_ASSOC);

        $result = [];
        foreach ($screens as $screen) {
            $result[$screen['id']] = $screen['name'];
        }

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


<?php

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

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

/**
 * This handles the rendering of models to labels.
 */
class ModelToLabelListener extends AbstractListener
{
    /**
     * The translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

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

        $this->translator = $translator;
    }

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

        if ($event->getModel()->getProperty('isdefault')) {
            $event->setLabel(
                sprintf(
                    '%s [%s]',
                    $event->getLabel(),
                    $this->translator->trans('MSC.fallback', [], 'contao_default')
                )
            );
        }
    }
}


<?php

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

namespace MetaModels\Filter;

use MetaModels\IMetaModel;

/**
 * This is the MetaModel filter interface.
 */
class Filter implements IFilter
{
    /**
     * The corresponding MetaModel.
     *
     * @var string
     */
    protected $strMetaModel = '';

    /**
     * The contained filter rules.
     *
     * @var array
     */
    protected $arrFilterRules = array();

    /**
     * The cached result after this filter has been evaluated.
     *
     * @var string[]|null
     */
    protected $arrMatches = null;

    /**
     * Create a new filter instance.
     *
     * @param IMetaModel $objMetaModel The MetaModel this filter shall apply to.
     */
    public function __construct(IMetaModel $objMetaModel)
    {
        if ($objMetaModel) {
            $this->strMetaModel = $objMetaModel->getTableName();
        }
    }

    /**
     * Clone the filter rule list.
     *
     * @return void
     */
    public function __clone()
    {
        $this->arrMatches     = null;
        $arrOld               = $this->arrFilterRules;
        $this->arrFilterRules = array();
        foreach ($arrOld as $objFilterRule) {
            $this->addFilterRule(clone $objFilterRule);
        }
    }

    /**
     * Create a copy of this filter.
     *
     * @return Filter|IFilter
     */
    public function createCopy()
    {
        $objCopy = clone $this;

        return $objCopy;
    }

    /**
     * {@inheritdoc}
     */
    public function addFilterRule(IFilterRule $objFilterRule)
    {
        // Reset matches as they are most likely invalid now.
        $this->arrMatches = null;

        $this->arrFilterRules[] = $objFilterRule;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getMatchingIds()
    {
        if ($this->arrMatches !== null) {
            return $this->arrMatches;
        }

        $arrIds = null;
        foreach ($this->arrFilterRules as $objFilterRule) {
            /** @var IFilterRule $objFilterRule */
            $arrRuleIds = $objFilterRule->getMatchingIds();
            if ($arrRuleIds === null) {
                continue;
            }
            // The first rule determines the master ids.
            if ($arrIds === null) {
                $arrIds = $arrRuleIds;
            } else {
                // NOTE: all rules are implicitely "AND"-ed together.
                $arrIds = array_intersect($arrIds, $arrRuleIds);
                // When no ids are left any more, the result will stay empty, do not evaluate any further rules.
                if (count($arrIds) == 0) {
                    break;
                }
            }
        }
        $this->arrMatches = $arrIds;

        return $arrIds;
    }
}