<?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     Richard Henkenjohann <richardhenkenjohann@googlemail.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 Contao\Config;
use Contao\CoreBundle\Framework\Adapter;
use Contao\CoreBundle\Routing\UrlGenerator;
use Contao\PageModel;
use Contao\System;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * This class builds filter URLs.
 */
class FilterUrlBuilder
{
    /**
     * The URL generator.
     *
     * @var UrlGenerator
     */
    private $urlGenerator;

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

    /**
     * Flag if the locale is prepended.
     *
     * @var bool
     */
    private $isLocalePrepended = true;

    /**
     * The Contao URL suffix.
     *
     * @var string
     */
    private $urlSuffix = '.html';

    /**
     * The page model adapter.
     *
     * @var Adapter|PageModel
     */
    private $pageModelAdapter;

    /**
     * Create a new instance.
     *
     * @param UrlGenerator $urlGenerator      The Contao URL generator.
     * @param RequestStack $requestStack      The request stack.
     * @param bool         $isLocalePrepended Flag if the locale is prepended to the URL.
     * @param string       $urlSuffix         The URL suffix.
     * @param Adapter      $pageModelAdapter  The page model adapter.
     */
    public function __construct(
        UrlGenerator $urlGenerator,
        RequestStack $requestStack,
        bool $isLocalePrepended,
        string $urlSuffix,
        Adapter $pageModelAdapter
    ) {
        $this->urlGenerator      = $urlGenerator;
        $this->requestStack      = $requestStack;
        $this->isLocalePrepended = $isLocalePrepended;
        $this->urlSuffix         = $urlSuffix;
        $this->pageModelAdapter  = $pageModelAdapter;
    }

    /**
     * Generate a frontend url.
     *
     * @param FilterUrl $filterUrl The filter URL.
     *
     * @return string
     */
    public function generate(FilterUrl $filterUrl)
    {
        $jumpTo = $filterUrl->getPage();

        // If no alias given, stay on current page.
        if (empty($jumpTo['alias'])) {
            $this->addFromCurrentRequest($filterUrl = $filterUrl->clone(), []);

            $jumpTo = $filterUrl->getPage();
        }
        $alias = $jumpTo['alias'];

        $parameters = $filterUrl->getGetParameters();

        $url = $alias;
        if ($filterUrl->hasSlug('auto_item')) {
            $url .= '/' . $this->encodeForAllowEncodedSlashes($filterUrl->getSlug('auto_item'));
        }

        if (!empty($jumpTo['domain'])) {
            $parameters['_domain'] = $jumpTo['domain'];
        }
        if (!empty($jumpTo['rootUseSSL'])) {
            $parameters['_ssl'] = (bool) $jumpTo['rootUseSSL'];
        }

        if ($filterUrl->hasSlug('language')) {
            $parameters['_locale'] = $filterUrl->getSlug('language');
        }
        foreach ($filterUrl->getSlugParameters() as $name => $value) {
            if (in_array($name, ['language', 'auto_item'])) {
                continue;
            }

            // Encode slashes in slugs - otherwise Symfony won't handle them correctly.
            // This mitigates for http://httpd.apache.org/docs/current/mod/core.html#allowencodedslashes
            // If not given, apache would 404 otherwise.
            $url .= '/' . $this->encodeForAllowEncodedSlashes($name) .
                '/' . $this->encodeForAllowEncodedSlashes($value);
        }

        return $this->urlGenerator->generate($url, $parameters);
    }

    /**
     * Generate a filter URL from the current request.
     *
     * @param array $options The options for updating - for details see FilterUrlBuilder::addFromCurrentRequest().
     *
     * @return FilterUrl
     */
    public function getCurrentFilterUrl($options = null): FilterUrl
    {
        $this->addFromCurrentRequest($filterUrl = new FilterUrl(), $options);

        return $filterUrl;
    }

    /**
     * Split the current request into fragments, strip the URL suffix, recreate the $_GET array and return the page ID
     *
     * This is mostly based on \Contao\Frontend::getPageIdFromUrl() but stripped off of some checks.
     *
     * Options may be:
     *   bool postAsSlug  Fields of POST data that shall be added to the slug entries.
     *                    default: []
     *   bool postAsGet   Fields of POST data that shall be added to the GET entries.
     *                    default: []
     *   bool preserveGet Flag if the GET parameters shall be added to the filter URL.
     *                    default: true
     *
     * @param FilterUrl $filterUrl The filter URL to update.
     * @param array     $options   The options for updating.
     *
     * @return void
     */
    public function addFromCurrentRequest(FilterUrl $filterUrl, $options = null): void
    {
        if (null === $options) {
            $options = [
                'postAsSlug'  => [],
                'postAsGet'   => [],
                'preserveGet' => true
            ];
        }

        $request = $this->requestStack->getMasterRequest();

        if (isset($options['preserveGet'])) {
            foreach ($request->query->all() as $name => $value) {
                $filterUrl->setGet($name, $value);
            }
        }

        if (null === $fragments = $this->determineFragments($request)) {
            return;
        }

        $filterUrl->setPageValue('alias', $fragments[0]);
        // Add the fragments to the slug array
        for ($i = 1, $c = \count($fragments); $i < $c; $i += 2) {
            // Skip key value pairs if the key is empty (see contao/core/#4702)
            if ('' === $fragments[$i]) {
                continue;
            }

            // Decode slashes in slugs - They got encoded in generate() above.
            $filterUrl->setSlug(
                $this->decodeForAllowEncodedSlashes($fragments[$i]),
                $this->decodeForAllowEncodedSlashes($fragments[($i + 1)])
            );
        }

        $this->extractPostData($filterUrl, $options, $request);
    }

    /**
     * Mitigate for apache AllowEncodedSlashes directive being off.
     *
     * @param string $value The value to mitigate for.
     *
     * @return string
     *
     * @see http://httpd.apache.org/docs/current/mod/core.html#allowencodedslashes
     */
    private function encodeForAllowEncodedSlashes(string $value): string
    {
        return str_replace(['/', '\\'], ['%2F', '%5C'], $value);
    }

    /**
     * Mitigate for apache AllowEncodedSlashes directive being off.
     *
     * @param string $value The value to mitigate for.
     *
     * @return string
     *
     * @see http://httpd.apache.org/docs/current/mod/core.html#allowencodedslashes
     */
    private function decodeForAllowEncodedSlashes(string $value): string
    {
        return str_replace(['%2F', '%5C'], ['/', '\\'], $value);
    }

    /**
     * Determine the fragments for the passed request.
     *
     * @param Request $request The request to parse.
     *
     * @return array|null
     *
     * @@SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @@SuppressWarnings(PHPMD.NPathComplexity)
     */
    private function determineFragments(Request $request): ?array
    {
        if (null === $requestUri = $this->strippedUri($request)) {
            return null;
        }

        $fragments = null;
        // Use folder-style URLs
        if (Config::get('folderUrl') && false !== strpos($requestUri, '/')) {
            $fragments = $this->getFolderUrlFragments(
                $requestUri,
                $request->getHost(),
                $request->attributes->get('_locale')
            );
        }

        // If folderUrl is deactivated or did not find a matching page
        if (null === $fragments) {
            if ('/' === $requestUri) {
                return null;
            }
            $fragments = explode('/', $requestUri);
        }

        // Add the second fragment as auto_item if the number of fragments is even
        if (Config::get('useAutoItem') && 0 === (\count($fragments) % 2)) {
            array_insert($fragments, 1, ['auto_item']);
        }

        $fragments = $this->getPageIdFromUrlHook($fragments);

        // Return if the alias is empty (see #4702 and #4972)
        if (null === $fragments || ('' === $fragments[0] && \count($fragments) > 1)) {
            return null;
        }

        return $fragments;
    }

    /**
     * Strip the leading locale (if any).
     *
     * @param Request $request The request.
     *
     * @return string
     */
    private function strippedUri(Request $request): ?string
    {
        // Strip leading slash.
        if (null === $request || '' === $requestUri = rawurldecode(substr($request->getPathInfo(), 1))) {
            return null;
        }
        if ($this->isLocalePrepended) {
            $matches = [];
            // Use the matches instead of substr() (thanks to Mario Müller)
            if (preg_match('@^([a-z]{2}(-[A-Z]{2})?)/(.*)$@', $requestUri, $matches)) {
                $requestUri = $matches[3];
            }
        }

        // Remove the URL suffix if not just a language root (e.g. en/) is requested
        if ('' !== $this->urlSuffix && '' !== $requestUri && (
                !$this->isLocalePrepended || !preg_match('@^[a-z]{2}(-[A-Z]{2})?/$@', $requestUri)
            )) {
            $requestUri = substr($requestUri, 0, -\strlen($this->urlSuffix));
        }

        return $requestUri;
    }

    /**
     * Update the fragments for folder URL aliases.
     *
     * @param string      $alias  The relative request.
     * @param string      $host   The host part of the current request.
     * @param string|null $locale The current locale or null if none requested.
     *
     * @return array
     */
    private function getFolderUrlFragments(string $alias, string $host, string $locale = null): ?array
    {
        // Check if there are pages with a matching alias
        $pages = $this->getPageCandidates($alias);
        if (null === $pages) {
            return null;
        }

        // Look for a root page whose domain name matches the host name
        if (isset($pages[$host])) {
            $languages = $pages[$host];
        } else {
            // empty domain
            $languages = $pages['*'] ?: [];
        }
        unset($pages);

        $pages = [];

        if (!$this->isLocalePrepended) {
            // Use the first result (see #4872)
            $pages = current($languages);
        } elseif ($locale && isset($languages[$locale])) {
            // Try to find a page matching the language parameter
            $pages = $languages[$locale];
        }

        // Return if there are no matches
        if (empty($pages)) {
            return null;
        }

        /** @var PageModel $page */
        $page = $pages[0];

        // The request consists of the alias only
        if ($alias == $page->alias) {
            $arrFragments = [$alias];
        } else {
            // Remove the alias from the request string, explode it and then re-insert it at the beginning.
            $arrFragments = explode('/', substr($alias, (\strlen($page->alias) + 1)));
            array_unshift($arrFragments, $page->alias);
        }

        return $arrFragments;
    }

    /**
     * Fetch matching page candidates.
     *
     * @param string $alias The requested alias.
     *
     * @return array|null
     */
    private function getPageCandidates(string $alias)
    {
        $aliases = [$alias];
        // Compile all possible aliases by applying dirname() to the request.
        while ('/' !== $alias && false !== strpos($alias, '/')) {
            $alias     = \dirname($alias);
            $aliases[] = $alias;
        }

        // Check if there are pages with a matching alias
        $pages = $this->pageModelAdapter->findByAliases($aliases);
        if (null === $pages) {
            return null;
        }
        $arrPages = [];
        // Order by domain and language
        while ($pages->next()) {
            /** @var PageModel $objModel */
            $objModel = $pages->current();
            $objPage  = $objModel->loadDetails();
            $domain   = $objPage->domain ?: '*';

            $arrPages[$domain][$objPage->rootLanguage][] = $objPage;
            // Also store the fallback language
            if ($objPage->rootIsFallback) {
                $arrPages[$domain]['*'][] = $objPage;
            }
        }
        return $arrPages;
    }

    /**
     * Call the getPageIdFromUrl HOOKs.
     *
     * @param array|null $fragments The input fragments.
     *
     * @return array|null
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getPageIdFromUrlHook(?array $fragments): ?array
    {
        if (!(isset($GLOBALS['TL_HOOKS']['getPageIdFromUrl']) && \is_array($GLOBALS['TL_HOOKS']['getPageIdFromUrl']))) {
            return $fragments;
        }
        foreach ($GLOBALS['TL_HOOKS']['getPageIdFromUrl'] as $callback) {
            $fragments = System::importStatic($callback[0])->{$callback[1]}($fragments);
        }

        return $fragments;
    }

    /**
     * Extract POST data from the passed request.
     *
     * @param FilterUrl $filterUrl The filter URL to populate.
     * @param array     $options   The options.
     * @param Request   $request   The request.
     *
     * @return void
     */
    private function extractPostData(FilterUrl $filterUrl, $options, Request $request): void
    {
        if (empty($options['postAsSlug']) && empty($options['postAsGet'])) {
            return;
        }

        foreach ($request->request->all() as $name => $value) {
            if (is_array($value)) {
                $value = implode(',', $value);
            }
            if (in_array($name, $options['postAsSlug'])) {
                $filterUrl->setSlug($name, $value);
            }
            if (in_array($name, $options['postAsGet'])) {
                $filterUrl->setGet($name, $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\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
 * This pass adds the tagged factories to the MetaModels factories.
 */
class CollectFactoriesPass implements CompilerPassInterface
{
    /**
     * The tag name to use for attribute factories.
     */
    const TAG_ATTRIBUTE_FACTORY = 'metamodels.attribute_factory';

    /**
     * The tag name to use for filter factories.
     */
    const TAG_FILTER_FACTORY = 'metamodels.filter_factory';

    /**
     * The tag name to use for property condition factories.
     */
    const TAG_PROPERTY_CONDITION_FACTORY = 'metamodels.condition_factory';

    /**
     * {@inheritDoc}
     */
    public function process(ContainerBuilder $container)
    {
        $this->collectAttributeFactories($container);
        $this->collectFilterFactories($container);
        $this->collectPropertyConditionFactories($container);
    }

    /**
     * Collect all tagged attribute factories.
     *
     * @param ContainerBuilder $container The container builder.
     *
     * @return void
     */
    private function collectAttributeFactories($container)
    {
        $attributeFactory = $container->getDefinition('metamodels.attribute_factory');
        foreach (array_keys($container->findTaggedServiceIds(self::TAG_ATTRIBUTE_FACTORY)) as $factory) {
            $attributeFactory->addMethodCall('addTypeFactory', [new Reference($factory)]);
        }
    }

    /**
     * Collect all tagged filter factories.
     *
     * @param ContainerBuilder $container The container builder.
     *
     * @return void
     */
    private function collectFilterFactories($container)
    {
        $attributeFactory = $container->getDefinition('metamodels.filter_setting_factory');
        foreach (array_keys($container->findTaggedServiceIds(self::TAG_FILTER_FACTORY)) as $factory) {
            $attributeFactory->addMethodCall('addTypeFactory', [new Reference($factory)]);
        }
    }

    /**
     * Collect all tagged filter factories.
     *
     * @param ContainerBuilder $container The container builder.
     *
     * @return void
     */
    private function collectPropertyConditionFactories($container)
    {
        $factories = $container->getDefinition('metamodels.core_bundle.dc_general.property_condition_factories');
        $args      = $factories->getArgument(0);
        foreach ($container->findTaggedServiceIds(self::TAG_PROPERTY_CONDITION_FACTORY) as $factory => $tags) {
            foreach ($tags as $tag) {
                $args[$tag['type']] = new Reference($factory);
            }
        }

        $factories->setArgument(0, $args);
    }
}


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

namespace MetaModels\Helper;

use ContaoCommunityAlliance\DcGeneral\Contao\InputProvider;
use Doctrine\DBAL\Connection;
use MetaModels\Exceptions\Database\ColumnDoesNotExistException;
use MetaModels\Exceptions\Database\ColumnExistsException;
use MetaModels\Exceptions\Database\InvalidColumnNameException;
use MetaModels\Exceptions\Database\InvalidTableNameException;
use MetaModels\Exceptions\Database\TableDoesNotExistException;
use MetaModels\Exceptions\Database\TableExistsException;

/**
 * This is the class for table manipulations like creation/renaming/deleting of tables and columns.
 *
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class TableManipulator
{
    /**
     * SQL statement template to create a table.
     * First parameter is the table name.
     */
    const STATEMENT_CREATE_TABLE = '
            CREATE TABLE `%s` (
                `id` int(10) unsigned NOT NULL auto_increment,
                `pid` int(10) unsigned NOT NULL default \'0\',
                `sorting` int(10) unsigned NOT NULL default \'0\',
                `tstamp` int(10) unsigned NOT NULL default \'0\',
                PRIMARY KEY  (`id`)
            ) ENGINE=MyISAM DEFAULT CHARSET=utf8';

    /**
     * SQL statement template to rename a table.
     * First parameter is the old name, second parameter is the new name.
     */
    const STATEMENT_RENAME_TABLE = 'ALTER TABLE `%s` RENAME TO `%s`';

    /**
     * SQL statement template to drop a table.
     * First parameter is the table name of the table to drop.
     */
    const STATEMENT_DROP_TABLE = 'DROP TABLE `%s`';

    /**
     * SQL statement template to rename a column of a table.
     * First parameter is table name.
     * Second parameter is the old name of the column.
     * Third parameter is the new name of the column.
     * Fourth parameter is the new type of the column.
     */
    const STATEMENT_RENAME_COLUMN = 'ALTER TABLE %s CHANGE COLUMN %s %s %s';

    /**
     * SQL statement template to add a column to a table.
     * First parameter is the table name.
     * Second parameter is the column name.
     * Third parameter is the type of the new column.
     */
    const STATEMENT_CREATE_COLUMN = 'ALTER TABLE %s ADD %s %s';

    /**
     * SQL statement template to delete a column from a table.
     * First parameter is the name of the column.
     */
    const STATEMENT_DROP_COLUMN = 'ALTER TABLE %s DROP COLUMN %s';

    /**
     * SQL statement template to add a index to a column of a table.
     * First parameter is table name.
     * second parameter is indextype
     * third parameter is name of the column.
     */
    const STATEMENT_ADD_INDEX_COLUMN = 'ALTER TABLE %s ADD %s(%s)';

    /**
     * List of reserved MySQL identifiers.
     *
     * @var string[]
     */
    protected static $reservedWords = array(
        // MySQL 5.5 and previous.
        'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT',
        'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE',
        'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME',
        'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND',
        'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE',
        'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF',
        'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR',
        'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERAL', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY',
        'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE',
        'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL',
        'INTO', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR',
        'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY',
        'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT',
        'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NOT',
        'NO_WRITE_TO_BINLOG', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT',
        'OUTER', 'OUTFILE', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READS', 'READ_WRITE',
        'REAL', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT',
        'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE',
        'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQLEXCEPTION',
        'SQLSTATE', 'SQLWARNING', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SSL', 'STARTING',
        'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER',
        'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE',
        'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'WHEN', 'WHERE',
        'WHILE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL',
        // New in MySQL 5.6.
        'GET', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS, MASTER_BIND', 'ONE_SHOT', 'PARTITION', 'SQL_AFTER_GTIDS',
        'SQL_BEFORE_GTIDS',
        // New in MySQL 5.7.
        'NONBLOCKING',
    );

    /**
     * List of reserved column post fix.
     *
     * @var string[]
     */
    protected static $reservedColumnPostFix = array('__sort');

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

    /**
     * All system columns that always are defined in a MetaModel table.
     *
     * When you alter this, ensure to also change @link{TableManipulatior::STATEMENT_CREATE_TABLE} above.
     *
     * @var string[]
     */
    private $systemColumns;

    /**
     * TableManipulator constructor.
     *
     * @param Connection $connection    Database connection.
     * @param array      $systemColumns System columns that always are defined in a MetaModel table and are not
     *                                  attributes.
     */
    public function __construct(Connection $connection, array $systemColumns)
    {
        $this->connection    = $connection;
        $this->systemColumns = $systemColumns;
    }

    /**
     * Test if the given word is a reserved MySQL word.
     *
     * @param string $word The word to test.
     *
     * @return bool
     */
    public function isReservedWord($word)
    {
        return in_array(strtoupper($word), self::$reservedWords);
    }

    /**
     * Test if the given column post fix is a reserved by MetaModels.
     *
     * @param string $strColName The column name to test.
     *
     * @return bool
     */
    public function isReserveColumnPostFix($strColName)
    {
        $inputProvider = new InputProvider();

        if (!$inputProvider->hasValue('colname')
            || strtolower($strColName) !== strtolower($inputProvider->getValue('colname'))
        ) {
            return false;
        }

        foreach (self::$reservedColumnPostFix as $postFix) {
            if ($postFix !== strtolower(substr($strColName, -strlen($postFix)))) {
                continue;
            }

            return true;
        }

        return false;
    }

    /**
     * Ensure that an identifier is valid in MySQL context.
     *
     * @param string $strName The identifier to check.
     *
     * @return bool
     */
    public function isValidMySQLIdentifier($strName)
    {
        // Match for valid table/column name, according to MySQL, a table name must start
        // with a letter and must be combined of letters, decimals and underscore.
        return (1 == preg_match('/^[a-z_][a-z\d_]*$/i', $strName));
    }

    /**
     * Checks whether the given table name is valid.
     *
     * @param string $strTableName The table name to check.
     *
     * @return bool true if the table name is valid, false otherwise.
     */
    public function isValidTablename($strTableName)
    {
        return $this->isValidMySQLIdentifier($strTableName) && !$this->isReservedWord($strTableName);
    }

    /**
     * Checks whether the column with the given name is a MetaModel system column.
     *
     * @param string $strColName The name of the column.
     *
     * @return bool true if the column is a system column, false otherwise.
     */
    public function isValidColumnName($strColName)
    {
        return $this->isValidMySQLIdentifier($strColName)
            && !$this->isReservedWord($strColName)
            && !$this->isReserveColumnPostFix($strColName);
    }

    /**
     * Checks whether the column with the given name is a MetaModel system column.
     *
     * @param string $strColName The name of the column.
     *
     * @return bool true if the column is a system column, false otherwise.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function isSystemColumn($strColName)
    {
        return in_array($strColName, $this->systemColumns);
    }

    /**
     * Checks whether the given table name is valid.
     *
     * @param string $strTableName The table name to check.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed.
     *
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function checkTablename($strTableName)
    {
        if (!$this->isValidTablename($strTableName)) {
            throw InvalidTableNameException::invalidCharacters($strTableName);
        }
    }

    /**
     * Checks whether the column with the given name is not a MetaModel system column and is a valid column name.
     *
     * If there is any problem, an Exception is raised, stating the nature of the error in the Exception message.
     *
     * @param string  $strColName        The name of the column.
     *
     * @param boolean $blnAllowSystemCol If this is set to true, no system column name checking will be applied.
     *
     * @return void
     *
     * @throws \Exception If an invalid column name has been passed.
     *
     * @see{MetaModelTableManipulation::isSystemColumn()} and @see{MetaModelTableManipulation::isValidColumnName()}.
     *
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function checkColumnName($strColName, $blnAllowSystemCol = false)
    {
        if (!$this->isValidColumnName($strColName)) {
            throw InvalidColumnNameException::invalidCharacters($strColName);
        }

        if ((!$blnAllowSystemCol) && $this->isSystemColumn($strColName)) {
            throw InvalidColumnNameException::systemColumn($strColName);
        }
    }

    /**
     * Checks whether the given table exists.
     *
     * @param string $strTableName The table name to check.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or the table does not exist.
     *
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function checkTableExists($strTableName)
    {
        $this->checkTablename($strTableName);
        if (!$this->connection->getSchemaManager()->tablesExist([$strTableName])) {
            throw TableDoesNotExistException::withName($strTableName);
        }
    }

    /**
     * Ensures that the given table does not exist.
     *
     * @param string $strTableName The table name to check.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or a table with the given name exists.
     *
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function checkTableDoesNotExist($strTableName)
    {
        $this->checkTablename($strTableName);
        if ($this->connection->getSchemaManager()->tablesExist([$strTableName])) {
            throw TableExistsException::withName($strTableName);
        }
    }

    /**
     * Creates a table with the given name.
     *
     * @param string $strTableName The name of the new table to create.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or a table with the given name exists.
     */
    public function createTable($strTableName)
    {
        $this->checkTableDoesNotExist($strTableName);
        $this->connection->query(sprintf(self::STATEMENT_CREATE_TABLE, $strTableName));
    }

    /**
     * Renames a table with the given name to the given new name.
     *
     * @param string $strTableName    The name of the table to rename.
     *
     * @param string $strNewTableName The name to which the table shall be renamed to.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed.
     */
    public function renameTable($strTableName, $strNewTableName)
    {
        $this->checkTableExists($strTableName);
        $this->checkTableDoesNotExist($strNewTableName);

        $this->connection->query(sprintf(self::STATEMENT_RENAME_TABLE, $strTableName, $strNewTableName));
    }

    /**
     * Deletes the table with the given name.
     *
     * @param string $strTableName The name of the new table to delete.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or the table does not exist.
     */
    public function deleteTable($strTableName)
    {
        $this->checkTableExists($strTableName);

        $this->connection->query(sprintf(self::STATEMENT_DROP_TABLE, $strTableName));
    }

    /**
     * Add a index to given tablename for specified columnname
     *
     * @param string $strTableName The table name.
     *
     * @param string $strIndexType The index type.
     *
     * @param string $strColName   The column name to add a index.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or the table does not exist, the column name is
     *                    invalid or the column does not exist.
     */
    public function addIndex($strTableName, $strIndexType, $strColName)
    {
        $this->checkColumnExists($strTableName, $strColName);
        $this->connection->query(
            sprintf(
                self::STATEMENT_ADD_INDEX_COLUMN,
                $strTableName,
                $strIndexType,
                $strColName
            )
        );
    }

    /**
     * Checks whether the given table exists.
     *
     * @param string  $strTableName      The table name to check.
     *
     * @param string  $strColName        The column name to check.
     *
     * @param boolean $blnAllowSystemCol If this is set to true, no system column name checking will be applied.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or the table does not exist, the column name is
     *                    invalid or the column does not exist.
     *
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function checkColumnExists($strTableName, $strColName, $blnAllowSystemCol = false)
    {
        $this->checkTableExists($strTableName);
        $this->checkColumnName($strColName, $blnAllowSystemCol);

        if (!$this->fieldExists($strTableName, $strColName)) {
            throw ColumnDoesNotExistException::withName($strColName, $strTableName);
        }
    }

    /**
     * Checks whether the given column does not exist.
     *
     * @param string  $strTableName      The table name to check.
     *
     * @param string  $strColName        The column name to check.
     *
     * @param boolean $blnAllowSystemCol If this is set to true, no system column name checking will be applied.
     *
     * @return void
     *
     * @throws \Exception If an invalid table name has been passed or the table does not exist, the column name is
     *                    invalid or the column does not exist.
     *
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function checkColumnDoesNotExist($strTableName, $strColName, $blnAllowSystemCol = false)
    {
        $this->checkTableExists($strTableName);
        $this->checkColumnName($strColName, $blnAllowSystemCol);

        if ($this->fieldExists($strColName, $strTableName)) {
            throw ColumnExistsException::withName($strColName, $strTableName);
        }
    }

    /**
     * Add a column to a table.
     *
     * Throws Exception if the table does not exist, the column name is invalid or the column already exists.
     *
     * @param string  $strTableName      The name of the table to add the column to.
     *
     * @param string  $strColumnName     The name of the new column.
     *
     * @param string  $strType           The SQL type notation of the new column.
     *
     * @param boolean $blnAllowSystemCol If this is set to true, no system column name checking will be applied.
     *
     * @return void
     */
    public function createColumn($strTableName, $strColumnName, $strType, $blnAllowSystemCol = false)
    {
        $this->checkColumnDoesNotExist($strTableName, $strColumnName, $blnAllowSystemCol);
        $this->connection->query(
            sprintf(
                self::STATEMENT_CREATE_COLUMN,
                $strTableName,
                $strColumnName,
                $strType
            )
        );
    }

    /**
     * Rename a column and/or change it's type in a table.
     *
     * Throws Exception if the table does not exist, the column name is invalid or the column already exists.
     *
     * @param string  $strTableName      The name of the table the column is in.
     *
     * @param string  $strColumnName     The current name of the column to be renamed.
     *
     * @param string  $strNewColumnName  The new name for the column.
     *
     * @param string  $strNewType        The new SQL type notation of the column.
     *
     * @param boolean $blnAllowSystemCol If this is set to true, no system column name checking will be applied.
     *
     * @return void
     */
    public function renameColumn(
        $strTableName,
        $strColumnName,
        $strNewColumnName,
        $strNewType,
        $blnAllowSystemCol = false
    ) {
        if ($strColumnName != $strNewColumnName) {
            $this->checkColumnExists($strTableName, $strColumnName, $blnAllowSystemCol);
            $this->checkColumnDoesNotExist($strTableName, $strNewColumnName, $blnAllowSystemCol);
        }
        $this->connection->query(
            sprintf(
                self::STATEMENT_RENAME_COLUMN,
                $strTableName,
                $strColumnName,
                $strNewColumnName,
                $strNewType
            )
        );
    }

    /**
     * Delete a column from a table.
     *
     * Throws Exception if the table does not exist, the column name is invalid or the column does not exist.
     *
     * @param string  $strTableName      The name of the table the column is in.
     *
     * @param string  $strColumnName     The name of the column to drop.
     *
     * @param boolean $blnAllowSystemCol If this is set to true, no system column name checking will be applied.
     *
     * @return void
     */
    public function dropColumn($strTableName, $strColumnName, $blnAllowSystemCol = false)
    {
        $this->checkColumnExists($strTableName, $strColumnName, $blnAllowSystemCol);
        $this->connection->query(
            sprintf(
                self::STATEMENT_DROP_COLUMN,
                $strTableName,
                $strColumnName
            )
        );
    }

    /**
     * Enables or disables Variant support on a certain MetaModel table.
     *
     * @param string $strTableName      The table name of the MetaModel.
     *
     * @param bool   $blnVariantSupport Flag if the support shall be turned on or off.
     *
     * @return void
     */
    public function setVariantSupport($strTableName, $blnVariantSupport)
    {
        if ($blnVariantSupport) {
            if ($this->connection->getSchemaManager()->tablesExist([$strTableName])
                && (!$this->fieldExists($strTableName, 'varbase'))) {
                $this->createColumn($strTableName, 'varbase', 'char(1) NOT NULL default \'\'', true);
                $this->createColumn($strTableName, 'vargroup', 'int(11) NOT NULL default 0', true);

                // If there is pre-existing data in the table, we need to provide a separate 'vargroup' value to all of
                // them, we can do this safely by setting all vargroups to the id of the base item.
                $this->connection->query(sprintf('UPDATE %s SET vargroup=id, varbase=1', $strTableName));
            }
        } else {
            if ($this->connection->getSchemaManager()->tablesExist([$strTableName])
                && $this->fieldExists($strTableName, 'varbase')
            ) {
                $this->dropColumn($strTableName, 'varbase', true);
                $this->dropColumn($strTableName, 'vargroup', true);
            }
        }
    }

    /**
     * Check is a table column exists.
     *
     * @param string $strTableName  Table name.
     * @param string $strColumnName Column name.
     *
     * @return bool
     */
    private function fieldExists($strTableName, $strColumnName)
    {
        $columns = $this->connection->getSchemaManager()->listTableColumns($strTableName);

        return isset($columns[$strColumnName]);
    }
}


<?php

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

namespace MetaModels\Widgets;

use Contao\FileSelector;
use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralInvalidArgumentException;

/**
 * Provide methods to handle input field "file tree".
 */
class FileSelectorWidget extends FileSelector
{
    /**
     * Initialize the object.
     *
     * @param array $arrAttributes An optional attributes array.
     *
     * @throws DcGeneralInvalidArgumentException When the property could not be retrieved from the DcGeneral.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function __construct($arrAttributes = null)
    {
        parent::__construct($arrAttributes);

        if (!$this->strField) {
            $this->strField = $arrAttributes['name'];
        }

        // Strip the leading "ctrl_" from the field name if present.
        if (substr($this->strField, 0, 5) === 'ctrl_') {
            $chunks = explode('_', $this->strField);
            array_shift($chunks);
            $this->strField = implode('_', $chunks);
        }
    }

    /**
     * Generate a particular sub part of the file tree and return it as HTML string.
     *
     * @param string $folder   The folder name.
     *
     * @param string $strField The property name.
     *
     * @param int    $level    The level where the given folder shall be rendered within.
     *
     * @param bool   $mount    Flag determining if the passed folder shall be handled as root level
     *                         (optional, default: no).
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function generateAjax($folder, $strField, $level, $mount = false)
    {
        return parent::generateAjax($folder, $this->strField, $level, $mount);
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use MetaModels\Filter\Setting\IFilterSettingFactory;

/**
 * This class provides the type options.
 */
class TypeOptionListener
{
    /**
     * The filter setting factory.
     *
     * @var IFilterSettingFactory
     */
    private $filterFactory;

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

    /**
     * Provide options for default selection.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     */
    public function handle(GetPropertyOptionsEvent $event)
    {
        if (('tl_metamodel_filtersetting' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('type' !== $event->getPropertyName())) {
            return;
        }

        $translator = $event->getEnvironment()->getTranslator();
        $options    = [];
        foreach ($this->filterFactory->getTypeNames() as $filter) {
            $options[$filter] = $translator->translate('typenames.' . $filter, 'tl_metamodel_filtersetting');
        }

        $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>
 * @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;

use Contao\CoreBundle\Routing\ScopeMatcher;
use MetaModels\BackendIntegration\Module;
use MetaModels\ViewCombination\ViewCombination;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

/**
 * This listens on the user and initializes the backend then.
 */
class UserListener
{
    /**
     * The authentication resolver.
     *
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * The authentication resolver.
     *
     * @var AuthenticationTrustResolverInterface
     */
    private $authenticationTrustResolver;

    /**
     * The scope matcher.
     *
     * @var ScopeMatcher
     */
    private $scopeMatcher;

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

    /**
     * Constructor.
     *
     * @param TokenStorageInterface                $tokenStorage                The token storage.
     * @param AuthenticationTrustResolverInterface $authenticationTrustResolver The authentication resolver.
     * @param ScopeMatcher                         $scopeMatcher                The scope matche.
     * @param ViewCombination                      $viewCombination             The view combination.
     */
    public function __construct(
        TokenStorageInterface $tokenStorage,
        AuthenticationTrustResolverInterface $authenticationTrustResolver,
        ScopeMatcher $scopeMatcher,
        ViewCombination $viewCombination
    ) {
        $this->tokenStorage                = $tokenStorage;
        $this->authenticationTrustResolver = $authenticationTrustResolver;
        $this->scopeMatcher                = $scopeMatcher;
        $this->viewCombination             = $viewCombination;
    }

    /**
     * Replaces the current session data with the stored session data.
     *
     * @param GetResponseEvent $event The event.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     *
     * @return void
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        if (!$this->scopeMatcher->isBackendMasterRequest($event)) {
            return;
        }

        $token = $this->tokenStorage->getToken();

        if (null === $token || $this->authenticationTrustResolver->isAnonymous($token)) {
            return;
        }

        $localMenu = &$GLOBALS['BE_MOD'];
        $this->buildBackendModules($localMenu);
        $this->injectChildTables($localMenu);
    }

    /**
     * Add the modules to the backend sections.
     *
     * @param array $localMenu Reference to the global array.
     *
     * @return void
     */
    private function buildBackendModules(&$localMenu)
    {
        foreach ($this->viewCombination->getStandalone() as $metaModelName => $screen) {
            $section = $screen['meta']['backendsection'];
            if (!isset($localMenu[$section])) {
                $localMenu[$section] = [];
            }
            if (!isset($localMenu[$section]['metamodel_' . $metaModelName])) {
                $localMenu[$section]['metamodel_' . $metaModelName] = ['tables' => []];
            }
            $localMenu[$section]['metamodel_' . $metaModelName]['callback'] = Module::class;
            array_unshift($localMenu[$section]['metamodel_' . $metaModelName]['tables'], $metaModelName);
        }
    }

    /**
     * Inject all child tables.
     *
     * @param array $localMenu Reference to the global array.
     *
     * @return void
     */
    private function injectChildTables(&$localMenu)
    {
        $parented  = $this->viewCombination->getParented();
        $lastCount = count($parented);
        while ($parented) {
            foreach ($parented as $metaModelName => $child) {
                foreach ($localMenu as $groupName => $modules) {
                    foreach ($modules as $moduleName => $module) {
                        if (isset($module['tables']) && in_array($child['meta']['ptable'], $module['tables'])) {
                            $localMenu[$groupName][$moduleName]['tables'][] = $metaModelName;
                            unset($parented[$metaModelName]);
                            break;
                        }
                    }
                }
            }
            // If the dependencies can not be resolved any further, we give up here to prevent an endless loop.
            if (count($parented) == $lastCount) {
                break;
            }
            $lastCount = count($parented);
        }
    }
}


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

namespace MetaModels\FrontendIntegration;

use Contao\System;
use MetaModels\Filter\FilterUrlBuilder;
use MetaModels\Filter\Setting\ICollection;
use MetaModels\Filter\Setting\IFilterSettingFactory;

/**
 * FE-module for FE-filtering.
 *
 * @property \FrontendTemplate $Template
 * @property string            $metamodel_jumpTo
 * @property string            $metamodel_fef_template
 * @property bool              $metamodel_fef_autosubmit
 * @property bool              $metamodel_fef_hideclearfilter
 * @property bool              $metamodel_available_values
 * @property string            $metamodel_fef_params
 * @property int               $id
 */
class HybridFilterBlock extends MetaModelHybrid
{
    /**
     * Template.
     *
     * @var string
     */
    protected $strTemplate = 'mm_filter_default';

    /**
     * The jumpTo page.
     *
     * @var array
     */
    private $arrJumpTo;

    /**
     * The database connection.
     *
     * @var IFilterSettingFactory
     */
    private $filterFactory;

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

    /**
     * Get the jump to page data.
     *
     * @return array
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function getJumpTo()
    {
        if (!isset($this->arrJumpTo)) {
            /** @var \Database\Result $page */
            $page = $GLOBALS['objPage'];
            $this->setJumpTo($page->row());

            if ($this->metamodel_jumpTo) {
                // Page to jump to when filter submit.
                $statement = $this->getConnection()->prepare('SELECT id, alias FROM tl_page WHERE id=? LIMIT 0,1');
                $statement->bindValue(1, $this->metamodel_jumpTo);
                $statement->execute();

                if ($statement->rowCount()) {
                    $this->setJumpTo($statement->fetch(\PDO::FETCH_ASSOC));
                }
            }
        }

        return $this->arrJumpTo;
    }

    /**
     * Set the jump to page data.
     *
     * @param array $arrJumpTo The page data.
     *
     * @return HybridFilterBlock
     */
    public function setJumpTo($arrJumpTo)
    {
        $this->arrJumpTo = $arrJumpTo;

        return $this;
    }

    /**
     * Retrieve the filter collection.
     *
     * @return ICollection
     */
    public function getFilterCollection()
    {
        return $this
            ->getFilterFactory()
            ->createCollection($this->metamodel_filtering);
    }

    /**
     * Display a wildcard in the back end.
     *
     * @return string
     */
    public function generate()
    {
        // Get template if configured.
        if ($this->metamodel_fef_template) {
            $this->strTemplate = $this->metamodel_fef_template;
        }

        return parent::generate();
    }

    /**
     * Generate the module.
     *
     * @return void
     */
    protected function compile()
    {
        $objFilter = new FrontendFilter($this->getConnection(), $this->getFilterUrlBuilder());
        $arrFilter = $objFilter->getMetaModelFrontendFilter($this);

        $this->Template->setData(array_merge($this->Template->getData(), $arrFilter));
        $this->Template->submit = $arrFilter['submit'];
    }

    /**
     * Obtain the filter setting factory.
     *
     * @return IFilterSettingFactory
     */
    private function getFilterFactory(): IFilterSettingFactory
    {
        if (null === $this->filterFactory) {
            return $this->filterFactory = System::getContainer()->get('metamodels.filter_setting_factory');
        }

        return $this->filterFactory;
    }

    /**
     * Obtain the filter URL builder.
     *
     * @return FilterUrlBuilder
     */
    private function getFilterUrlBuilder(): FilterUrlBuilder
    {
        if (null === $this->filterUrlBuilder) {
            return $this->filterUrlBuilder = System::getContainer()->get('metamodels.filter_url');
        }

        return $this->filterUrlBuilder;
    }
}


<?php

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

namespace MetaModels\DcGeneral\Data;

use Contao\System;
use ContaoCommunityAlliance\DcGeneral\Data\ConfigInterface;
use Doctrine\DBAL\Connection;
use MetaModels\Attribute\IAttribute;
use MetaModels\Filter\IFilter;
use MetaModels\Filter\Rules\Comparing\GreaterThan;
use MetaModels\Filter\Rules\Comparing\LessThan;
use MetaModels\Filter\Rules\Condition\ConditionAnd;
use MetaModels\Filter\Rules\Condition\ConditionOr;
use MetaModels\Filter\Rules\SearchAttribute;
use MetaModels\Filter\Rules\SimpleQuery;
use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;

/**
 * Class to generate a MetaModels filter from a data configuration.
 */
class FilterBuilder
{
    /**
     * The MetaModel instance.
     *
     * @var IMetaModel
     */
    protected $metaModel;

    /**
     * The configuration.
     *
     * @var ConfigInterface
     */
    protected $configuration;

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

    /**
     * Generate a filter from a passed configuration.
     *
     * @param IMetaModel      $metaModel     The MetaModel.
     *
     * @param ConfigInterface $configuration The data configuration.
     *
     * @param Connection|null $connection    The database connection.
     */
    public function __construct(IMetaModel $metaModel, ConfigInterface $configuration, Connection $connection = null)
    {
        $this->metaModel     = $metaModel;
        $this->configuration = $configuration;

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

        $this->connection = $connection;
    }

    /**
     * Retrieve the MetaModel.
     *
     * @return IMetaModel
     */
    protected function getMetaModel()
    {
        return $this->metaModel;
    }

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

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

    /**
     * Build the sub query for a comparing operator like =,<,>.
     *
     * @param IAttribute $attribute The attribute.
     *
     * @param IFilter    $filter    The filter to add the operations to.
     *
     * @param array      $operation The operation to convert.
     *
     * @return void
     *
     * @throws \RuntimeException When the operation can not be parsed.
     */
    private function getFilterForComparingOperator($attribute, IFilter $filter, $operation)
    {
        if ($attribute) {
            switch ($operation['operation']) {
                case '=':
                    $filter->addFilterRule(new SearchAttribute(
                        $attribute,
                        $operation['value'],
                        $this->getMetaModel()->getAvailableLanguages() ?: array()
                    ));
                    return;

                case '>':
                    $filter->addFilterRule(new GreaterThan(
                        $attribute,
                        $operation['value']
                    ));
                    return;

                case '<':
                    $filter->addFilterRule(new LessThan(
                        $attribute,
                        $operation['value']
                    ));
                    return;

                default:
                    throw new \RuntimeException(
                        'Error processing filter array - unknown operation ' .
                        var_export($operation['operation'], true),
                        1
                    );
            }
        }

        $columns = $this->connection->getSchemaManager()->listTableColumns($this->getMetaModel()->getTableName());
        if ($columns[$operation['property']]) {
            // System column?
            $filter->addFilterRule(new SimpleQuery(
                sprintf(
                    'SELECT id FROM %s WHERE %s %s?',
                    $this->getMetaModel()->getTableName(),
                    $operation['property'],
                    $operation['operation']
                ),
                array($operation['value']),
                'id',
                $this->connection
            ));

            return;
        }

        throw new \RuntimeException(
            'Error processing filter array - unknown property ' . var_export($operation['property'], true),
            1
        );
    }

    /**
     * Return the filter query for a "foo IN ('a', 'b')" filter.
     *
     * @param IFilter $filter    The filter to add the operations to.
     *
     * @param array   $operation The operation to convert.
     *
     * @return void
     *
     * @throws \RuntimeException When the operation can not be parsed.
     */
    private function getFilterForInList(IFilter $filter, $operation)
    {
        // Rewrite the IN operation to a rephrased term: "(x=a) OR (x=b) OR ...".
        $subRules = array();
        foreach ($operation['values'] as $varValue) {
            $subRules[] = array(
                'property'  => $operation['property'],
                'operation' => '=',
                'value'     => $varValue
            );
        }
        $this->calculateSubfilter(array(
            'operation' => 'OR',
            'children'    => $subRules
        ), $filter);
    }

    /**
     * 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 IAttribute $attribute The attribute.
     *
     * @param IFilter    $filter    The filter to add the operations to.
     *
     * @param array      $operation The operation to convert.
     *
     * @return void
     *
     * @throws \RuntimeException When the operation can not be parsed.
     */
    private function getFilterForLike($attribute, IFilter $filter, $operation)
    {
        if ($attribute) {
            $filter->addFilterRule(new SearchAttribute(
                $attribute,
                $operation['value'],
                $this->getMetaModel()->getAvailableLanguages() ?: array()
            ));

            return;
        }

        $columns = $this->connection->getSchemaManager()->listTableColumns($this->getMetaModel()->getTableName());
        if ($columns[$operation['property']]) {
            // System column?
            $filter->addFilterRule(new SimpleQuery(
                sprintf(
                    'SELECT id FROM %s WHERE %s LIKE ?',
                    $this->getMetaModel()->getTableName(),
                    $operation['property']
                ),
                array($operation['value']),
                'id',
                $this->connection
            ));

            return;
        }

        throw new \RuntimeException(
            'Error processing filter array - unknown property ' . var_export($operation['property'], true),
            1
        );
    }

    /**
     * Calculate a native SQL sub procedure.
     *
     * @param FilterBuilderSql $procedure The procedure to which to append to.
     *
     * @param array            $children  The children to calculate.
     *
     * @return array
     */
    protected function buildNativeSqlProcedure(FilterBuilderSql $procedure, $children)
    {
        $skipped   = array();
        $metaModel = $this->getMetaModel();
        $tableName = $metaModel->getTableName();
        foreach ($children as $child) {
            // If there is an attribute contained within this rule, skip it.
            if (isset($child['property']) && $metaModel->hasAttribute($child['property'])) {
                $skipped[] = $child;

                continue;
            }

            // Try to parse the sub procedure and extract as much as possible.
            if (('AND' === $child['operation']) || ('OR' === $child['operation'])) {
                if (null === $child['children']) {
                    continue;
                }

                $subProcedure = new FilterBuilderSql($tableName, $child['operation'], $this->connection);
                $subSkipped   = $this->buildNativeSqlProcedure($subProcedure, $child['children']);

                if (count($subSkipped) !== count($child['children'])) {
                    $procedure->addSubProcedure($subProcedure);
                }

                if (!empty($subSkipped)) {
                    $skipped += $subSkipped;
                }

                continue;
            }

            $procedure->addChild($child);
        }

        return $skipped;
    }

    /**
     * Method to optimize as many system column lookup filters as possible into a combined filter rule.
     *
     * @param ConditionAnd|ConditionOr $filterRule The filter to which the optimized rule shall be added to.
     *
     * @param array                    $children   The children to parse.
     *
     * @param string                   $operation  The operation to parse (AND or OR).
     *
     * @return array
     */
    protected function optimizedFilter($filterRule, $children, $operation)
    {
        $procedure = new FilterBuilderSql($this->getMetaModel()->getTableName(), $operation, $this->connection);
        $skipped   = $this->buildNativeSqlProcedure($procedure, $children);

        if (!$procedure->isEmpty()) {
            $filterRule->addChild($this->getMetaModel()->getEmptyFilter()->addFilterRule($procedure->build()));
        }

        return $skipped;
    }

    /**
     * Build an AND or OR query.
     *
     * @param IFilter $filter    The filter to add the operations to.
     *
     * @param array   $operation The operation to convert.
     *
     * @return void
     */
    protected function getAndOrFilter(IFilter $filter, $operation)
    {
        if (!$operation['children']) {
            return;
        }

        if ($operation['operation'] == 'AND') {
            $filterRule = new ConditionAnd();
        } else {
            $filterRule = new ConditionOr();
        }
        $filter->addFilterRule($filterRule);

        $children = $this->optimizedFilter($filterRule, $operation['children'], $operation['operation']);

        foreach ($children as $child) {
            $subFilter = $this->getMetaModel()->getEmptyFilter();
            $filterRule->addChild($subFilter);
            $this->calculateSubfilter($child, $subFilter);
        }
    }

    /**
     * Retrieve the attribute for a filter operation.
     *
     * @param array $operation The operation to retrieve the attribute for.
     *
     * @return IAttribute
     *
     * @throws \InvalidArgumentException When the attribute can not be retrieved.
     */
    protected function getAttributeFromFilterOperation($operation)
    {
        $attribute = null;
        if (!empty($operation['property'])) {
            $attribute = $this->getMetaModel()->getAttribute($operation['property']);
        }

        if ($attribute === null) {
            throw new \InvalidArgumentException('Attribute ' . $operation['property'] . ' not found.');
        }

        return $attribute;
    }

    /**
     * Combine a filter in standard filter array notation.
     *
     * Supported operations are:
     * operation      needed arguments     argument type.
     * AND
     *                'children'           array
     * OR
     *                'children'           array
     * =
     *                'property'           string (the name of a property)
     *                'value'              literal
     * >
     *                'property'           string (the name of a property)
     *                'value'              literal
     * <
     *                'property'           string (the name of a property)
     *                'value'              literal
     * IN
     *                'property'           string (the name of a property)
     *                'values'             array of literal
     *
     * @param array   $operation The filter to be combined into the passed filter object.
     *
     * @param IFilter $filter    The filter object where the rules shall get appended to.
     *
     * @return void
     *
     * @throws \RuntimeException When an improper filter condition is encountered, an exception is thrown.
     */
    private function calculateSubfilter($operation, IFilter $filter)
    {
        if (!is_array($operation)) {
            throw new \RuntimeException('Error Processing subfilter: ' . var_export($operation, true), 1);
        }

        switch ($operation['operation']) {
            case 'AND':
            case 'OR':
                $this->getAndOrFilter($filter, $operation);
                break;

            case '=':
            case '>':
            case '<':
                $this->getFilterForComparingOperator(
                    $this->getAttributeFromFilterOperation($operation),
                    $filter,
                    $operation
                );
                break;

            case 'IN':
                $this->getFilterForInList($filter, $operation);
                break;

            case 'LIKE':
                $this->getFilterForLike(
                    $this->getAttributeFromFilterOperation($operation),
                    $filter,
                    $operation
                );
                break;

            default:
                throw new \RuntimeException(
                    'Error processing filter array - unknown operation ' . var_export($operation, true),
                    1
                );
        }
    }

    /**
     * Prepare a filter and return it.
     *
     * @return IFilter
     */
    public function build()
    {
        $filter = $this->getMetaModel()->getEmptyFilter();

        if ($this->configuration->getFilter()) {
            $this->calculateSubfilter(
                array
                (
                    'operation' => 'AND',
                    'children' => $this->configuration->getFilter()
                ),
                $filter
            );
        }

        return $filter;
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\DecodePropertyValueForWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use MetaModels\Filter\Setting\IFilterSettingFactory;

/**
 * This class provides the attribute options and encodes and decodes the attribute id.
 */
class AttributeListener
{
    /**
     * The filter setting factory.
     *
     * @var IFilterSettingFactory
     */
    private $filterFactory;

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

    /**
     * Provide options for default selection.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     */
    public function getOptions(GetPropertyOptionsEvent $event)
    {
        if (('tl_metamodel_filtersetting' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('attr_id' !== $event->getPropertyName())) {
            return;
        }

        $result      = [];
        $model       = $event->getModel();
        $metaModel   = $this->filterFactory->createCollection($model->getProperty('fid'))->getMetaModel();
        $typeFactory = $this->filterFactory->getTypeFactory($model->getProperty('type'));

        $typeFilter = null;
        if ($typeFactory) {
            $typeFilter = $typeFactory->getKnownAttributeTypes();
        }

        foreach ($metaModel->getAttributes() as $attribute) {
            $typeName = $attribute->get('type');

            if ($typeFilter && (!in_array($typeName, $typeFilter))) {
                continue;
            }

            $strSelectVal          = $metaModel->getTableName() .'_' . $attribute->getColName();
            $result[$strSelectVal] = $attribute->getName() . ' [' . $typeName . ']';
        }

        $event->setOptions($result);
    }

    /**
     * Translates an attribute id to a generated alias {@see getAttributeNames()}.
     *
     * @param DecodePropertyValueForWidgetEvent $event The event.
     *
     * @return void
     */
    public function decodeValue(DecodePropertyValueForWidgetEvent $event)
    {
        if (('tl_metamodel_filtersetting' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('attr_id' !== $event->getProperty())) {
            return;
        }

        $model     = $event->getModel();
        $metaModel = $this->filterFactory->createCollection($model->getProperty('fid'))->getMetaModel();
        $value     = $event->getValue();

        if (!($metaModel && $value)) {
            return;
        }

        $attribute = $metaModel->getAttributeById($value);
        if ($attribute) {
            $event->setValue($metaModel->getTableName() .'_' . $attribute->getColName());
        }
    }

    /**
     * Translates an generated alias {@see getAttributeNames()} to the corresponding attribute id.
     *
     * @param EncodePropertyValueFromWidgetEvent $event The event.
     *
     * @return void
     */
    public function encodeValue(EncodePropertyValueFromWidgetEvent $event)
    {
        if (('tl_metamodel_filtersetting' !== $event->getEnvironment()->getDataDefinition()->getName())
            || ('attr_id' !== $event->getProperty())) {
            return;
        }

        $model     = $event->getModel();
        $metaModel = $this->filterFactory->createCollection($model->getProperty('fid'))->getMetaModel();
        $value     = $event->getValue();

        if (!($metaModel && $value)) {
            return;
        }

        $value = substr($value, strlen($metaModel->getTableName() . '_'));

        $attribute = $metaModel->getAttribute($value);

        if ($attribute) {
            $event->setValue($attribute->get('id'));
        }
    }
}


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\MetaModel;

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\BackendViewInterface;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\ContaoBackendViewTemplate;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\EditMask;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetOperationButtonEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\DcGeneral\Event\ActionEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PostCreateModelEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PreCreateModelEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PreEditModelEvent;
use MetaModels\DcGeneral\Data\Model;
use MetaModels\IFactory;

/**
 * Event handler class to manage the "create variant" button.
 */
class CreateVariantButtonListener
{
    /**
     * The factory.
     *
     * @var IFactory
     */
    private $factory;

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

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

    /**
     * Check if we have to add the "Create variant" button.
     *
     * @param GetOperationButtonEvent $event The event.
     *
     * @return void
     */
    public function createButton(GetOperationButtonEvent $event)
    {
        if ('createvariant' !== $event->getCommand()->getName()) {
            return;
        }
        /** @var Model $model */
        $model     = $event->getModel();
        $metamodel = $model->getItem()->getMetaModel();

        if (!$metamodel->hasVariants() || $model->getProperty('varbase') === '0') {
            $event->setHtml('');
        }
    }

    /**
     * Handle the "create variant" event.
     *
     * @param ActionEvent $event The action Event being executed.
     *
     * @return void
     *
     * @throws \RuntimeException When the base model can not be found.
     * @throws \InvalidArgumentException When the view in the environment is incompatible.
     */
    public function handleCreateVariantAction(ActionEvent $event)
    {
        if (false === $this->scopeMatcher->currentScopeIsBackend()
            || 'createvariant' !== $event->getAction()->getName()) {
            return;
        }

        $environment   = $event->getEnvironment();
        $view          = $environment->getView();
        $dataProvider  = $environment->getDataProvider();
        $inputProvider = $environment->getInputProvider();
        $modelId       = $inputProvider->hasParameter('id')
            ? ModelId::fromSerialized($inputProvider->getParameter('id'))
            : null;

        /** @var \MetaModels\DcGeneral\Data\Driver $dataProvider */
        $model = $dataProvider
            ->createVariant(
                $dataProvider
                    ->getEmptyConfig()
                    ->setId($modelId->getId())
            );

        if ($model == null) {
            throw new \RuntimeException(sprintf(
                'Could not find model with id %s for creating a variant.',
                $modelId
            ));
        }

        $metaModel = $this->factory->getMetaModel($model->getProviderName());

        if (!$metaModel || !$metaModel->hasVariants()) {
            return;
        }

        $preFunction = function ($environment, $model) {
            /** @var EnvironmentInterface $environment */
            $copyEvent = new PreCreateModelEvent($environment, $model);
            $environment->getEventDispatcher()->dispatch($copyEvent::NAME, $copyEvent);
        };

        $postFunction = function ($environment, $model) {
            /** @var EnvironmentInterface $environment */
            $copyEvent = new PostCreateModelEvent($environment, $model);
            $environment->getEventDispatcher()->dispatch($copyEvent::NAME, $copyEvent);
        };

        if (!$view instanceof BackendViewInterface) {
            throw new \InvalidArgumentException('Invalid view registered in environment.');
        }
        $editMask = new EditMask($view, $model, null, $preFunction, $postFunction, $this->breadcrumb($environment));
        $event->setResponse($editMask->execute());
    }

    /**
     * Check the items before the edit start. If there is a item with variant support and a empty vargroup it must be a
     * base. So set the varbase to 1.
     *
     * @param PreEditModelEvent $event The event with the model.
     *
     * @return void
     */
    public function presetVariantBase(PreEditModelEvent $event)
    {
        $model = $event->getModel();

        // Check of we have the driver from MetaModels. Only these request are from interest.
        if (!$model instanceof Model) {
            return;
        }

        // Get the item and check the context.
        $nativeItem = $model->getItem();
        $metaModel  = $nativeItem->getMetaModel();

        if ($metaModel->hasVariants() && (!$nativeItem->get('vargroup'))) {
            $nativeItem->set('varbase', '1');
        }
    }

    /**
     * Get the breadcrumb navigation via event.
     *
     * @param EnvironmentInterface $environment The environment.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function breadcrumb(EnvironmentInterface $environment)
    {
        $event = new GetBreadcrumbEvent($environment);

        $environment->getEventDispatcher()->dispatch($event::NAME, $event);

        $arrReturn = $event->getElements();

        if (!is_array($arrReturn) || count($arrReturn) == 0) {
            return null;
        }

        $GLOBALS['TL_CSS'][] = 'bundles/ccadcgeneral/css/generalBreadcrumb.css';

        $objTemplate           = new ContaoBackendViewTemplate('dcbe_general_breadcrumb');
        $objTemplate->elements = $arrReturn;

        return $objTemplate->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     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\Attribute;

use MetaModels\Attribute\Events\CollectMetaModelAttributeInformationEvent;
use MetaModels\Attribute\Events\CreateAttributeEvent;
use MetaModels\Attribute\Events\CreateAttributeFactoryEvent;
use MetaModels\IMetaModel;
use MetaModels\IMetaModelsServiceContainer;
use MetaModels\MetaModelsEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * This is the implementation of the Field factory to query instances of fields.
 *
 * Usually this is only used internally by {@link MetaModels\Factory}
 */
class AttributeFactory implements IAttributeFactory
{
    /**
     * The service container.
     *
     * @var IMetaModelsServiceContainer
     */
    protected $serviceContainer;

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

    /**
     * The registered type factories.
     *
     * @var IAttributeTypeFactory[]
     */
    protected $typeFactories = array();

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

    /**
     * Set the service container.
     *
     * @param IMetaModelsServiceContainer $serviceContainer  The service container to use.
     *
     * @param bool                        $deprecationNotice Determine deprecated notice.
     *
     * @return AttributeFactory
     *
     * @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::ATTRIBUTE_FACTORY_CREATE)) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Event "' .
                MetaModelsEvents::ATTRIBUTE_FACTORY_CREATE .
                '" is deprecated - register your attribute factories via the service container.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
            $this->eventDispatcher->dispatch(
                MetaModelsEvents::ATTRIBUTE_FACTORY_CREATE,
                new CreateAttributeFactoryEvent($this)
            );
        }

        return $this;
    }

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

    /**
     * Create an attribute instance from an information array.
     *
     * @param array      $information The attribute information.
     *
     * @param IMetaModel $metaModel   The MetaModel instance for which the attribute shall be created.
     *
     * @return IAttribute|null
     */
    public function createAttribute($information, $metaModel)
    {
        $event = new CreateAttributeEvent($information, $metaModel);
        $this->eventDispatcher->dispatch(CreateAttributeEvent::NAME, $event);

        if ($event->getAttribute()) {
            return $event->getAttribute();
        }

        $factory = $this->getTypeFactory($information['type']);

        if (!$factory) {
            return null;
        }

        return $factory->createInstance($information, $metaModel);
    }

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

        $this->typeFactories[$typeName] = $typeFactory;

        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function attributeTypeMatchesFlags($name, $flags)
    {
        $factory = $this->getTypeFactory($name);

        // Shortcut, if all are valid, return all. :)
        if ($flags === self::FLAG_ALL) {
            return true;
        }

        return (($flags & self::FLAG_INCLUDE_TRANSLATED) && $factory->isTranslatedType())
            || (($flags & self::FLAG_INCLUDE_SIMPLE) && $factory->isSimpleType())
            || (($flags & self::FLAG_INCLUDE_COMPLEX) && $factory->isComplexType());
    }

    /**
     * {@inheritdoc}
     */
    public function getTypeNames($flags = false)
    {
        if ($flags === false) {
            $flags = self::FLAG_ALL;
        }

        $result = array();
        foreach (array_keys($this->typeFactories) as $name) {
            if (!$this->attributeTypeMatchesFlags($name, $flags)) {
                continue;
            }

            $result[] = $name;
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function collectAttributeInformation(IMetaModel $metaModel)
    {
        $event = new CollectMetaModelAttributeInformationEvent($metaModel);

        $this->eventDispatcher->dispatch($event::NAME, $event);

        return $event->getAttributeInformation();
    }

    /**
     * {@inheritdoc}
     */
    public function createAttributesForMetaModel($metaModel)
    {
        $attributes = array();
        foreach ($this->collectAttributeInformation($metaModel) as $information) {
            $attribute = $this->createAttribute($information, $metaModel);
            if ($attribute) {
                $attributes[] = $attribute;
            }
        }

        return $attributes;
    }

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


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetBreadcrumbEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;

/**
 * This class renders various breadcrumbs.
 */
abstract class AbstractBreadcrumbListener
{
    /**
     * The breadcrumb store factory.
     *
     * @var BreadcrumbStoreFactory
     */
    private $storeFactory;

    /**
     * The parent element renderer.
     *
     * @var AbstractBreadcrumbListener
     */
    private $parent;

    /**
     * Create a new instance.
     *
     * @param BreadcrumbStoreFactory          $storeFactory The store factory.
     * @param AbstractBreadcrumbListener|null $parent       Optional parent renderer.
     */
    public function __construct(
        BreadcrumbStoreFactory $storeFactory,
        AbstractBreadcrumbListener $parent = null
    ) {
        $this->storeFactory = $storeFactory;
        $this->parent       = $parent;
    }

    /**
     * Event handler.
     *
     * @param GetBreadcrumbEvent $event The event.
     *
     * @return void
     */
    public function getBreadcrumb(GetBreadcrumbEvent $event)
    {
        $environment = $event->getEnvironment();
        if (!$this->wantToHandle($event)) {
            return;
        }

        $elements = $this->storeFactory->createStore();
        $this->getBreadcrumbElements($environment, $elements);
        $event->setElements($elements->getElements());
        $event->stopPropagation();
    }

    /**
     * Test if we want to handle the event.
     *
     * @param GetBreadcrumbEvent $event The event.
     *
     * @return bool
     */
    abstract protected function wantToHandle(GetBreadcrumbEvent $event);

    /**
     * Perform the bread crumb generating.
     *
     * @param EnvironmentInterface $environment The environment in use.
     *
     * @param BreadcrumbStore      $elements    The elements generated so far.
     *
     * @return void
     */
    protected function getBreadcrumbElements(EnvironmentInterface $environment, BreadcrumbStore $elements)
    {
        if ($this->parent) {
            $this->parent->getBreadcrumbElements($environment, $elements);
        }
    }

    /**
     * Extract the id value from the serialized parameter with the given name.
     *
     * @param EnvironmentInterface $environment   The environment.
     *
     * @param string               $parameterName The parameter name containing the id.
     *
     * @return int
     */
    protected function extractIdFrom(EnvironmentInterface $environment, $parameterName = 'pid')
    {
        $parameter = $environment->getInputProvider()->getParameter($parameterName);

        return ModelId::fromSerialized($parameter)->getId();
    }
}


<?php

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

use ContaoCommunityAlliance\DcGeneral\Event\AbstractModelAwareEvent;
use MetaModels\BackendIntegration\PurgeCache;

/**
 * Central event subscriber implementation.
 */
class PurgeListener
{
    /**
     * The cache purger.
     *
     * @var PurgeCache
     */
    private $purger;

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

    /**
     * Check if we have to purge the MetaModels cache.
     *
     * @param AbstractModelAwareEvent $event The event holding the model being manipulated.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function checkPurge(AbstractModelAwareEvent $event)
    {
        $table = $event->getModel()->getProviderName();
        if (($table == 'tl_metamodel') ||
            ($table == 'tl_metamodel_dca') ||
            ($table == 'tl_metamodel_dca_sortgroup') ||
            ($table == 'tl_metamodel_dcasetting') ||
            ($table == 'tl_metamodel_dcasetting_condition') ||
            ($table == 'tl_metamodel_attribute') ||
            ($table == 'tl_metamodel_filter') ||
            ($table == 'tl_metamodel_filtersetting') ||
            ($table == 'tl_metamodel_rendersettings') ||
            ($table == 'tl_metamodel_rendersetting') ||
            ($table == 'tl_metamodel_dca_combine')
        ) {
            $this->purger->purge();
        }
    }
}