<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\DefinitionBuilder;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\FilterBuilder;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\ParentChildCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ModelRelationship\ParentChildConditionInterface;

/**
 * This class is the abstract base for the condition builders.
 */
class ConditionBuilderWithVariants extends AbstractConditionBuilder
{
    /**
     * The real calculating function.
     *
     * @return void
     */
    protected function calculate()
    {
        // Basic conditions.
        $this->addHierarchicalConditions();
        $this->addParentCondition();

        // Conditions for metamodels variants.
        $relationship = $this->getRootCondition();
        $relationship->setSetters(array_merge_recursive(
            [['property' => 'varbase', 'value' => '1']],
            $relationship->getSetters()
        ));

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

        $builder->andPropertyEquals('varbase', 1);

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

        $setter  = [
            ['to_field' => 'varbase', 'value' => '0'],
            ['to_field' => 'vargroup', 'from_field' => 'vargroup']
        ];
        $inverse = [];

        /** @var ParentChildConditionInterface $relationship */
        $relationship = $this->definition->getChildCondition($this->container->getName(), $this->container->getName());

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

        $relationship
            ->setFilterArray(
                FilterBuilder::fromArray($relationship->getFilterArray())
                    ->getFilter()
                    ->getBuilder()
                    ->encapsulateOr()
                    ->andRemotePropertyEquals('vargroup', 'vargroup')
                    ->andRemotePropertyEquals('vargroup', 'id')
                    ->andRemotePropertyEquals('varbase', 0, true)
                    ->getAllAsArray()
            )
            ->setSetters($setter)
            ->setInverseFilterArray($inverse);
    }
}


<?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\Contao\Hooks;

use Contao\Controller;
use Contao\CoreBundle\Framework\Adapter;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use MetaModels\CoreBundle\Assets\IconBuilder;
use MetaModels\IFactory;
use MetaModels\IMetaModel;
use MetaModels\ViewCombination\ViewCombination;

/**
 * This class handles loading of the virtual data containers.
 */
class LoadDataContainer
{
    /**
     * Adapter to the Contao\Controller class.
     *
     * @var Controller
     */
    private $controller;

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

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

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

    /**
     * Create a new instance.
     *
     * @param IFactory        $factory           The MetaModels factory.
     * @param ViewCombination $combination       The view combination provider.
     * @param Adapter         $controllerAdapter The controller adapter to load languages and data containers.
     * @param IconBuilder     $iconBuilder       The icon builder.
     */
    public function __construct(
        IFactory $factory,
        ViewCombination $combination,
        Adapter $controllerAdapter,
        IconBuilder $iconBuilder
    ) {
        $this->factory     = $factory;
        $this->combination = $combination;
        $this->controller  = $controllerAdapter;
        $this->iconBuilder = $iconBuilder;
    }

    /**
     * Load a data container.
     *
     * @param string $tableName The table name.
     *
     * @return void
     */
    public function onLoadDataContainer($tableName)
    {
        static $tableExists;
        // Test that the tables have been created.
        if (null === $tableExists) {
            $tableExists = \System::getContainer()
                ->get('database_connection')
                ->getSchemaManager()
                ->tablesExist(['tl_metamodel']);
        }
        if (false === $tableExists) {
            return;
        }

        $this->handleMetaModelTable($tableName);
        $this->handleNonMetaModelTable($tableName);
    }

    /**
     * Handle for MetaModel tables - this loads the base definition "tl_metamodel_item".
     *
     * @param string $tableName The table name.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function handleMetaModelTable($tableName)
    {
        static $tableNames;
        if (!$tableNames) {
            $tableNames = $this->factory->collectNames();
        }
        // Not a MetaModel, get out now.
        if (!in_array($tableName, $tableNames)) {
            return;
        }

        $this->controller->loadLanguageFile('tl_metamodel_item');
        $this->controller->loadDataContainer('tl_metamodel_item');
        if (!isset($GLOBALS['TL_DCA'][$tableName])) {
            $GLOBALS['TL_DCA'][$tableName] = [];
        }

        $GLOBALS['TL_DCA'][$tableName] = array_replace_recursive(
            (array) $GLOBALS['TL_DCA']['tl_metamodel_item'],
            (array) $GLOBALS['TL_DCA'][$tableName]
        );
    }

    /**
     * Handle for non MetaModel tables - this adds the child operations.
     *
     * @param string $tableName The table name.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function handleNonMetaModelTable($tableName)
    {
        // Nothing to do for MetaModel tables.
        if (substr($tableName, 0, 3) === 'mm_') {
            return;
        }

        static $map;
        if (!isset($map)) {
            $map = $this->buildMap();
        }

        // No children for this table.
        if (!isset($map[$tableName])) {
            return;
        }

        $parentDCA = &$GLOBALS['TL_DCA'][$tableName];

        $this->controller->loadLanguageFile('default');
        foreach ($map[$tableName] as $metaModelTable => $inputScreen) {
            $metaModel = $this->factory->getMetaModel($metaModelTable);
            $caption   = $this->buildCaption($metaModel, $inputScreen);

            $operationName                                   = 'edit_' . $metaModel->getTableName();
            $parentDCA['list']['operations'][$operationName] = array
            (
                'label'      => &$caption,
                'href'       => 'table=' . $metaModelTable,
                'icon'       => $this->iconBuilder->getBackendIcon($inputScreen['meta']['backendicon']),
                'attributes' => 'onclick="Backend.getScrollOffset()"',
            );

            // Is the destination table a metamodel with variants?
            if ($metaModel->hasVariants()) {
                $parentDCA['list']['operations'][$operationName]['idparam'] = 'id_' . $tableName;
            } else {
                $parentDCA['list']['operations'][$operationName]['idparam'] = 'pid';
            }

            // Compatibility with DC_Table.
            if ($parentDCA['config']['dataContainer'] !== 'General') {
                $idParameter                                                        =
                    $parentDCA['list']['operations'][$operationName]['idparam'];
                $parentDCA['list']['operations'][$operationName]['button_callback'] =
                    function ($row, $href, $label, $name, $icon, $attributes, $table) use ($idParameter) {
                        return $this->buildChildOperationButton(
                            $idParameter,
                            $row['id'],
                            $href,
                            $label,
                            $name,
                            $icon,
                            $attributes,
                            $table
                        );
                    };
            }
        }
    }

    /**
     * Build the data container map.
     *
     * @return array
     */
    private function buildMap()
    {
        $map = [];
        foreach ($this->combination->getParented() as $childName => $child) {
            $map[$child['meta']['ptable']][$childName] = $child;
        }

        return $map;
    }

    /**
     * Build the caption for a table.
     *
     * @param IMetaModel $metaModel   The MetaModel to build the caption for.
     * @param array      $inputScreen The input screen information.
     *
     * @return array
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function buildCaption($metaModel, $inputScreen): array
    {
        $caption = [
            sprintf($GLOBALS['TL_LANG']['MSC']['metamodel_edit_as_child']['label'], $metaModel->getName()),
            ''
        ];

        foreach ($inputScreen['label'] as $langCode => $label) {
            if ($label !== '' && $langCode === $GLOBALS['TL_LANGUAGE']) {
                $caption = [
                    $label,
                    $inputScreen['description'][$langCode]
                ];
            }
        }

        return $caption;
    }

    /**
     * This method exists only for being compatible when MetaModels are being used as child table from DC_Table context.
     *
     * @param string $idParameter The id parameter in use.
     *
     * @param string $itemId      The current data row.
     *
     * @param string $href        The href to be appended.
     *
     * @param string $label       The operation label.
     *
     * @param string $name        The operation name.
     *
     * @param string $icon        The icon path.
     *
     * @param string $attributes  The button attributes.
     *
     * @param string $table       The table name.
     *
     * @return string
     */
    private function buildChildOperationButton($idParameter, $itemId, $href, $label, $name, $icon, $attributes, $table)
    {
        $modelId = ModelId::fromValues($table, $itemId);

        $url = $href . '&' . $idParameter . '=' . $modelId->getSerialized();
        // If id parameter different, we have to override it in the URL.
        if ('id' !== $idParameter) {
            $url .= '&id=';
        }
        $url = $this->controller->addToUrl($url);
        // If id parameter different, we have to clean out the id in the URL now.
        if ('id' !== $idParameter) {
            $url = preg_replace('#(&)id=(?:&)?#', '$1', $url);
        }

        $title = sprintf($label ?: $name, $itemId);
        return sprintf(
            '%4$s ',
            $url,
            specialchars($title),
            $attributes,
            $this->iconBuilder->getBackendIconImageTag($icon, $label)
        );
    }
}


<?php

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

namespace MetaModels;

use MetaModels\Attribute\IAttribute;
use MetaModels\Attribute\IInternal;
use MetaModels\Events\ParseItemEvent;
use MetaModels\Filter\IFilter;
use MetaModels\Render\Setting\ICollection;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Interface for a MetaModel item.
 *
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class Item implements IItem
{
    /**
     * The MetaModel instance attached to the item.
     *
     * Get's populated with the first call to getMetaModel() (lazy initialization).
     *
     * @var IMetaModel
     */
    protected $metaModel;

    /**
     * The data array containing the raw values obtained from the attributes.
     *
     * @var array
     */
    protected $arrData = array();

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

    /**
     * Create a new instance.
     *
     * @param IMetaModel                    $objMetaModel The model this item is represented by.
     * @param array|null                    $arrData      The initial data that shall be injected into the new instance.
     * @param EventDispatcherInterface|null $dispatcher   The event dispatcher.
     */
    public function __construct(IMetaModel $objMetaModel, $arrData, EventDispatcherInterface $dispatcher = null)
    {
        $this->arrData    = $arrData;
        $this->metaModel  = $objMetaModel;
        $this->dispatcher = $dispatcher;

        if (null === $dispatcher) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Not passing the event dispatcher as 3rd argument to "' . __METHOD__ . '" is deprecated ' .
                'and will cause an error in MetaModels 3.0',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
        }

        if (null === $arrData) {
            // Initialize attributes with empty values.
            $arrData = [];

            foreach ($objMetaModel->getAttributes() as $attribute) {
                $arrData[$attribute->getColName()] = null;
            }
        }

        $this->arrData   = $arrData;
        $this->metaModel = $objMetaModel;
    }

    /**
     * Retrieve the service container.
     *
     * @return IMetaModelsServiceContainer
     */
    public function getServiceContainer()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated and will get removed in MetaModels 3.0.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd
        return $this->getMetaModel()->getServiceContainer();
    }

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

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

    /**
     * Helper function for {@see MetaModelItem::parseValue()} and {@see MetaModelItem::parseAttribute()}.
     *
     * @param IAttribute       $objAttribute    The attribute to parse.
     *
     * @param string           $strOutputFormat The desired output format.
     *
     * @param ICollection|null $objSettings     The settings object to be applied.
     *
     * @return array The parsed information for the given attribute.
     */
    public function internalParseAttribute($objAttribute, $strOutputFormat, $objSettings)
    {
        if ($objAttribute instanceof IInternal) {
            return array();
        }

        $arrResult = array();

        if ($objAttribute) {
            // Extract view settings for this attribute.
            if ($objSettings) {
                $objAttributeSettings = $objSettings->getSetting($objAttribute->getColName());
            } else {
                $objAttributeSettings = null;
            }
            foreach ($objAttribute->parseValue(
                $this->arrData,
                $strOutputFormat,
                $objAttributeSettings
            ) as $strKey => $varValue) {
                $arrResult[$strKey] = $varValue;
            }
        }

        // If "hideEmptyValues" is true and the raw is empty remove text and output format.
        if (($objSettings instanceof ICollection)
            && $objSettings->get('hideEmptyValues')
            && $this->isEmptyValue($arrResult['raw'])
        ) {
            unset($arrResult[$strOutputFormat]);
            unset($arrResult['text']);
        }

        return $arrResult;
    }

    /**
     * Check if a value is empty.
     *
     * @param array $mixValue The value.
     *
     * @return boolean True => empty, false => found a valid values
     */
    protected function isEmptyValue($mixValue)
    {
        if (is_array($mixValue)) {
            return $this->isArrayEmpty($mixValue);
        } elseif ($mixValue === '') {
            return true;
        } elseif ($mixValue === null) {
            return true;
        }

        return false;
    }

    /**
     * Run through each level of an array and check if we have at least one empty value.
     *
     * @param array $arrArray The array to check.
     *
     * @return boolean True => empty, False => some values found.
     */
    protected function isArrayEmpty($arrArray)
    {
        // First off check for simple types.
        if (empty($arrArray)) {
            return true;
        }
        // Next check for a value array.
        if (is_array($arrArray) && array_key_exists('value', $arrArray)) {
            return $this->isArrayEmpty($arrArray['value']);
        }
        // Now check sub arrays.
        if (is_array($arrArray)) {
            foreach ($arrArray as $value) {
                if (is_array($value)) {
                    return $this->isArrayEmpty($value);
                } elseif (!empty($value)) {
                    return false;
                }
            }
        }

        return false;
    }

    /**
     * Return the native value of an attribute.
     *
     * @param string $strAttributeName The name of the attribute.
     *
     * @return mixed
     */
    public function get($strAttributeName)
    {
        return array_key_exists($strAttributeName, $this->arrData) ? $this->arrData[$strAttributeName] : null;
    }

    /**
     * Set the native value of an Attribute.
     *
     * @param string $strAttributeName The name of the attribute.
     *
     * @param mixed  $varValue         The value of the attribute.
     *
     * @return IItem
     */
    public function set($strAttributeName, $varValue)
    {
        $this->arrData[$strAttributeName] = $varValue;

        return $this;
    }

    /**
     * Fetch the MetaModel that this item is originating from.
     *
     * @return IMetaModel the instance.
     */
    public function getMetaModel()
    {
        return $this->metaModel;
    }

    /**
     * Fetch the MetaModel attribute instance with the given name.
     *
     * @param string $strAttributeName The name of the attribute.
     *
     * @return IAttribute The instance.
     */
    public function getAttribute($strAttributeName)
    {
        return $this->getMetaModel()->getAttribute($strAttributeName);
    }

    /**
     * Check if the given attribute is set. This mean if in the data array
     * is the filed set or not. If the attribute is not loaded the function
     * will return false.
     *
     * @param string $strAttributeName The desired attribute.
     *
     * @return bool True means the data is set, on load of the item or at any time.
     *              False means the attribute is not set.
     */
    public function isAttributeSet($strAttributeName)
    {
        return array_key_exists($strAttributeName, $this->arrData);
    }

    /**
     * Return a list of the col names from the attributes which are set.
     * Including all meta field as well.
     *
     * @return array
     */
    public function getSetAttributes()
    {
        return array_keys($this->arrData);
    }

    /**
     * Determines if this item is a variant of another item.
     *
     * @return bool True if it is an variant, false otherwise.
     */
    public function isVariant()
    {
        return $this->getMetaModel()->hasVariants() && ($this->arrData['varbase'] === '0');
    }

    /**
     * Determines if this item is variant base of other items.
     *
     * Note: this does not mean that there actually exist variants of
     * this item. It merely simply states, that this item is able
     * to function as variant base for other items.
     *
     * @return bool True if it is an variant base, false otherwise.
     */
    public function isVariantBase()
    {
        return $this->getMetaModel()->hasVariants() && ($this->arrData['varbase'] === '1');
    }

    /**
     * Fetch the meta model variants for this item.
     *
     * @param IFilter $objFilter The filter settings to be applied.
     *
     * @return IItems|null A list of all variants for this item.
     */
    public function getVariants($objFilter)
    {
        if ($this->isVariantBase()) {
            return $this->getMetaModel()->findVariants(array($this->get('id')), $objFilter);
        }

        return null;
    }

    /**
     * Fetch the meta model variant base for this item.
     *
     * Note: For a non-variant item the variant base is the item itself.
     *
     * @return IItem The variant base.
     */
    public function getVariantBase()
    {
        if ($this->getMetaModel()->hasVariants() && !$this->isVariantBase()) {
            return $this->getMetaModel()->findById($this->get('vargroup'));
        }

        return $this;
    }

    /**
     * Find all Variants including the variant base.
     *
     * The item itself is excluded from the return list.
     *
     * @param IFilter $objFilter The additional filter settings to apply.
     *
     * @return null|IItems
     */
    public function getSiblings($objFilter)
    {
        if (!$this->getMetaModel()->hasVariants()) {
            return null;
        }
        return $this->getMetaModel()->findVariantsWithBase(array($this->get('id')), $objFilter);
    }

    /**
     * Save the current data for every attribute to the data sink.
     *
     * @param int|null $timestamp Optional the timestamp.
     *
     * @return void
     */
    public function save($timestamp = null)
    {
        if (null === $timestamp) {
            // @codingStandardsIgnoreStart
            @\trigger_error(
                'Not passing a timestamp has been deprecated and will cause an error in MetaModels 3',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
        }

        $objMetaModel = $this->getMetaModel();
        $objMetaModel->saveItem($this, $timestamp);
    }

    /**
     * Register the assets in Contao.
     *
     * @param ICollection|null $objSettings The render settings to use.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function registerAssets($objSettings)
    {
        if (!$objSettings) {
            return;
        }

        // Include CSS.
        $arrCss = $objSettings->get('additionalCss');

        foreach ((array) $arrCss as $arrFile) {
            if ($arrFile['published']) {
                $GLOBALS['TL_CSS'][md5($arrFile['file'])] = $arrFile['file'];
            }
        }

        // Include JS.
        $arrJs = $objSettings->get('additionalJs');

        foreach ((array) $arrJs as $arrFile) {
            if ($arrFile['published']) {
                $GLOBALS['TL_JAVASCRIPT'][md5($arrFile['file'])] = $arrFile['file'];
            }
        }
    }

    /**
     * Renders the item in the given output format.
     *
     * @param string      $strOutputFormat The desired output format (optional - default: text).
     *
     * @param ICollection $objSettings     The render settings to use (optional - default: null).
     *
     * @return array attribute name => format => value
     */
    public function parseValue($strOutputFormat = 'text', $objSettings = null)
    {
        $this->registerAssets($objSettings);

        $arrResult = [
            'raw'            => $this->arrData,
            'text'           => [],
            'attributes'     => [],
            $strOutputFormat => [],
            'class'          => '',
            'actions'        => []
        ];

        // No render settings, parse "normal" and hope the best - not all attribute types must provide usable output.
        if (!$objSettings) {
            foreach ($this->getMetaModel()->getAttributes() as $objAttribute) {
                $arrResult['attributes'][$objAttribute->getColName()] = $objAttribute->getName();
                foreach ($this->internalParseAttribute($objAttribute, $strOutputFormat, null) as $strKey => $varValue) {
                    $arrResult[$strKey][$objAttribute->getColName()] = $varValue;
                }
            }
            return $arrResult;
        }

        // Add jumpTo link
        $jumpTo = $this->buildJumpToLink($objSettings);
        if (true === $jumpTo['deep']) {
            $arrResult['actions']['jumpTo'] = [
                'href'  => $jumpTo['url'],
                'label' => $this->getCaptionText('details'),
                'class' => 'details'
            ];
        }

        // Just here for backwards compatibility with templates. See #1087
        $arrResult['jumpTo'] = $jumpTo;

        // First, parse the values in the same order as they are in the render settings.
        foreach ($objSettings->getSettingNames() as $strAttrName) {
            $objAttribute = $this->getMetaModel()->getAttribute($strAttrName);
            if ($objAttribute) {
                $arrResult['attributes'][$objAttribute->getColName()] = $objAttribute->getName();
                foreach ($this->internalParseAttribute(
                    $objAttribute,
                    $strOutputFormat,
                    $objSettings
                ) as $strKey => $varValue) {
                    $arrResult[$strKey][$objAttribute->getColName()] = $varValue;
                }
            }
        }

        // Add css classes, i.e. for the frontend editing list.
        if ($this->getMetaModel()->hasVariants()) {
            $arrResult['class'] = $this->variantCssClass();
        }

        // Trigger event to allow other extensions to manipulate the parsed data.
        $event = new ParseItemEvent($objSettings, $this, $strOutputFormat, $arrResult);
        $this->getEventDispatcher()->dispatch(MetaModelsEvents::PARSE_ITEM, $event);

        return $event->getResult();
    }

    /**
     * Build the jumpTo link for use in templates.
     *
     * The returning array will hold the following keys:
     * * params - the url parameter (only if a valid filter setting could be determined).
     * * deep   - boolean true, if parameters are non empty, false otherwise.
     * * page   - id of the jumpTo page.
     * * url    - the complete generated url
     *
     * @param ICollection $objSettings The render settings to use.
     *
     * @return array
     */
    public function buildJumpToLink($objSettings)
    {
        if (!$objSettings) {
            return null;
        }

        return $objSettings->buildJumpToUrlFor($this);
    }

    /**
     * Renders a single attribute in the given output format.
     *
     * @param string      $strAttributeName The desired attribute.
     *
     * @param string      $strOutputFormat  The desired output format (optional - default: text).
     *
     * @param ICollection $objSettings      The render settings to use (optional - default: null).
     *
     * @return array format=>value
     */
    public function parseAttribute($strAttributeName, $strOutputFormat = 'text', $objSettings = null)
    {
        return $this->internalParseAttribute($this->getAttribute($strAttributeName), $strOutputFormat, $objSettings);
    }

    /**
     * Returns a new item containing the same values as this item but no id.
     *
     * This is useful when creating new items that shall be based upon another item.
     *
     * @return IItem the new copy.
     */
    public function copy()
    {
        // Fetch data, clean undesired fields and return the new item.
        $arrNewData = $this->arrData;
        unset($arrNewData['id']);
        unset($arrNewData['tstamp']);
        unset($arrNewData['vargroup']);
        return new Item($this->getMetaModel(), $arrNewData, $this->dispatcher);
    }

    /**
     * Returns a new item containing the same values as this item but no id.
     *
     * Additionally, the item will be a variant child of this item.
     *
     * NOTE: if this item is not a variant base itself, this item will return a item
     * that is a child of this items variant base. i.e. exact clone.
     *
     * @return \MetaModels\IItem the new copy.
     */
    public function varCopy()
    {
        $objNewItem = $this->copy();
        // If this item is a variant base, we need to clean the variant base and set ourselves as the base.
        if ($this->isVariantBase()) {
            $objNewItem->set('vargroup', $this->get('id'));
            $objNewItem->set('varbase', '0');
        } else {
            $objNewItem->set('vargroup', $this->get('vargroup'));
            $objNewItem->set('varbase', '0');
        }
        return $objNewItem;
    }

    /**
     * Retrieve the translation string for the given lang key.
     *
     * In order to achieve the correct caption text, the function tries several translation strings sequentially.
     * The first language key that is set will win, even if it is to be considered empty.
     *
     * This message is looked up in the following order:
     * 1. $GLOBALS['TL_LANG']['MSC'][<mm tablename>][<render settings id>][$langKey]
     * 2. $GLOBALS['TL_LANG']['MSC'][<mm tablename>][$langKey]
     * 3. $GLOBALS['TL_LANG']['MSC'][$langKey]
     *
     * @param string $langKey The language key to retrieve.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getCaptionText($langKey)
    {
        $tableName = $this->getMetaModel()->getTableName();
        if (isset($this->objView)
            && isset($GLOBALS['TL_LANG']['MSC'][$tableName][$this->objView->get('id')][$langKey])
        ) {
            return $GLOBALS['TL_LANG']['MSC'][$tableName][$this->objView->get('id')][$langKey];
        } elseif (isset($GLOBALS['TL_LANG']['MSC'][$tableName][$langKey])) {
            return $GLOBALS['TL_LANG']['MSC'][$tableName][$langKey];
        }

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

    /**
     * Create the CSS class for variant information.
     *
     * @return string
     */
    private function variantCssClass()
    {
        if ($this->isVariant()) {
            return 'variant';
        }
        if ($this->isVariantBase()) {
            $result = 'varbase';

            if (0 !== $this->getVariants(null)->getCount()) {
                $result .= ' varbase-with-variants';
            }
            return $result;
        }

        return '';
    }
}


<?php

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

namespace MetaModels\CoreBundle\DcGeneral;

use Contao\StringUtil;
use MetaModels\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyContainAnyOfCondition;
use MetaModels\IMetaModel;

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


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\DefinitionBuilder;

use MetaModels\DcGeneral\DataDefinition\Definition\IMetaModelDefinition;
use MetaModels\DcGeneral\DataDefinition\Definition\MetaModelDefinition;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\ViewCombination\ViewCombination;

/**
 * This class builds the MetaModel definition.
 */
class MetaModelDefinitionBuilder
{
    use MetaModelDefinitionBuilderTrait;

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

    /**
     * Create a new instance.
     *
     * @param ViewCombination $viewCombination The view combination.
     */
    public function __construct(ViewCombination $viewCombination)
    {
        $this->viewCombination = $viewCombination;
    }

    /**
     * Build the definition.
     *
     * @param IMetaModelDataDefinition $container The container being built.
     *
     * @return void
     */
    protected function build(IMetaModelDataDefinition $container)
    {
        $definition = $this->createOrGetDefinition($container);

        if (empty($combination = $this->viewCombination->getCombination($container->getName()))) {
            return;
        }

        if (!$definition->hasActiveRenderSetting()) {
            $definition->setActiveRenderSetting($combination['view_id']);
        }

        if (!$definition->hasActiveInputScreen()) {
            $definition->setActiveInputScreen($combination['dca_id']);
        }
    }

    /**
     * Create or get the MetaModel definition.
     *
     * @param IMetaModelDataDefinition $container The container being built.
     *
     * @return IMetaModelDefinition
     */
    private function createOrGetDefinition(IMetaModelDataDefinition $container)
    {
        if ($container->hasMetaModelDefinition()) {
            return $container->getMetaModelDefinition();
        }

        $container->setMetaModelDefinition($definition = new MetaModelDefinition());

        return $definition;
    }
}


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

/**
 * This is the class for table manipulations like creation/renaming/deleting of tables and columns.
 *
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class TableManipulation
{
    /**
     * 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,
                `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');

    /**
     * All system columns that always are defined in a MetaModel table.
     *
     * When you alter this, ensure to also change @link{TableManipulation::STATEMENT_CREATE_TABLE} above.
     *
     * @var string[]
     */
    protected static $systemColumns = array('id', 'pid', 'sorting', 'tstamp');

    /**
     * Returns the Contao database instance to use.
     *
     * @return \Database the database instance.
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected static function getDB()
    {
        return $GLOBALS['container']['metamodels-service-container']->getDatabase();
    }

    /**
     * Test if the given word is a reserved MySQL word.
     *
     * @param string $word The word to test.
     *
     * @return bool
     */
    public static 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 static 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 static 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 static function isValidTablename($strTableName)
    {
        return self::isValidMySQLIdentifier($strTableName) && !self::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 static function isValidColumnName($strColName)
    {
        return self::isValidMySQLIdentifier($strColName)
               && !self::isReservedWord($strColName)
               && !self::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 static function isSystemColumn($strColName)
    {
        return in_array($strColName, $GLOBALS['METAMODELS_SYSTEM_COLUMNS']);
    }

    /**
     * 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.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public static function checkTablename($strTableName)
    {
        if (!self::isValidTablename($strTableName)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['invalidTableName'], $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.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public static function checkColumnName($strColName, $blnAllowSystemCol = false)
    {
        if (!self::isValidColumnName($strColName)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['invalidColumnName'], $strColName));
        }

        if ((!$blnAllowSystemCol) && self::isSystemColumn($strColName)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['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.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public static function checkTableExists($strTableName)
    {
        self::checkTablename($strTableName);
        if (!self::getDB()->tableExists($strTableName, null, true)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['tableDoesNotExist'], $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.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public static function checkTableDoesNotExist($strTableName)
    {
        self::checkTablename($strTableName);
        if (self::getDB()->tableExists($strTableName, null, true)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['tableExists'], $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 static function createTable($strTableName)
    {
        self::checkTableDoesNotExist($strTableName);
        self::getDB()->execute(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 static function renameTable($strTableName, $strNewTableName)
    {
        self::checkTableExists($strTableName);
        self::checkTableDoesNotExist($strNewTableName);

        self::getDB()->execute(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 static function deleteTable($strTableName)
    {
        self::checkTableExists($strTableName);

        self::getDB()->execute(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 static function addIndex($strTableName, $strIndexType, $strColName)
    {
        self::checkColumnExists($strTableName, $strColName);
        self::getDB()->execute(
            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.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public static function checkColumnExists($strTableName, $strColName, $blnAllowSystemCol = false)
    {
        self::checkTableExists($strTableName);
        self::checkColumnName($strColName, $blnAllowSystemCol);
        if (!self::getDB()->fieldExists($strColName, $strTableName, true)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['columnDoesNotExist'], $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.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public static function checkColumnDoesNotExist($strTableName, $strColName, $blnAllowSystemCol = false)
    {
        self::checkTableExists($strTableName);
        self::checkColumnName($strColName, $blnAllowSystemCol);
        if (self::getDB()->fieldExists($strColName, $strTableName, true)) {
            throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['columnExists'], $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 static function createColumn($strTableName, $strColumnName, $strType, $blnAllowSystemCol = false)
    {
        self::checkColumnDoesNotExist($strTableName, $strColumnName, $blnAllowSystemCol);
        self::getDB()->execute(
            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 static function renameColumn(
        $strTableName,
        $strColumnName,
        $strNewColumnName,
        $strNewType,
        $blnAllowSystemCol = false
    ) {
        if ($strColumnName != $strNewColumnName) {
            self::checkColumnExists($strTableName, $strColumnName, $blnAllowSystemCol);
            self::checkColumnDoesNotExist($strTableName, $strNewColumnName, $blnAllowSystemCol);
        }
        self::getDB()->execute(
            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 static function dropColumn($strTableName, $strColumnName, $blnAllowSystemCol = false)
    {
        self::checkColumnExists($strTableName, $strColumnName, $blnAllowSystemCol);
        self::getDB()->execute(
            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 static function setVariantSupport($strTableName, $blnVariantSupport)
    {
        if ($blnVariantSupport) {
            if (self::getDB()->tableExists($strTableName, null, true)
                && (!self::getDB()->fieldExists('varbase', $strTableName, true))
            ) {
                self::createColumn($strTableName, 'varbase', 'char(1) NOT NULL default \'\'', true);
                self::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.
                self::getDB()->execute(sprintf('UPDATE %s SET vargroup=id, varbase=1', $strTableName));
            }
        } else {
            if (self::getDB()->tableExists($strTableName, null, true)
                && self::getDB()->fieldExists('varbase', $strTableName, true)
            ) {
                self::dropColumn($strTableName, 'varbase', true);
                self::dropColumn($strTableName, 'vargroup', true);
            }
        }
    }
}


<?php

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

namespace MetaModels\CoreBundle\Controller\Backend;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * This controller provides the add-all action in input screens.
 */
class SupportMetaModelsController
{
    /**
     * The twig engine.
     *
     * @var EngineInterface
     */
    private $templating;

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

    /**
     * The github contributors file.
     *
     * @var string
     */
    private $github;

    /**
     * The transifex contributors file.
     *
     * @var string
     */
    private $transifex;

    /**
     * Create a new instance.
     *
     * @param EngineInterface     $templating The twig engine.
     * @param TranslatorInterface $translator The translator.
     * @param string              $github     Path to the github contributor json list.
     * @param string              $transifex  Path to the transifex contributor json list.
     */
    public function __construct(EngineInterface $templating, TranslatorInterface $translator, $github, $transifex)
    {
        $this->templating = $templating;
        $this->translator = $translator;
        $this->github     = $github;
        $this->transifex  = $transifex;
    }

    /**
     * Invoke this.
     *
     * @return Response The template data.
     */
    public function __invoke()
    {
        return new Response(
            $this->templating->render(
                'MetaModelsCoreBundle::misc/support.html.twig',
                [
                    'stylesheets' => [
                        'bundles/metamodelscore/css/supportscreen.css'
                    ],
                    'headline' => $this->translator->trans('MOD.support_metamodels.0', [], 'contao_modules'),
                    'sub_headline' =>
                        $this->translator->trans('MSC.metamodels_support.main_headline', [], 'contao_default'),
                    'head_contributor' =>
                        $this->translator->trans('MSC.metamodels_support.contributor_headline', [], 'contao_default'),
                    'purpose' => $this->translator->trans('MSC.metamodels_support.purpose', [], 'contao_default'),
                    'other_donations' =>
                        $this->translator->trans('MSC.metamodels_support.other_donations', [], 'contao_default'),
                    'main_text' =>
                        $this->translator->trans('MSC.metamodels_support.main_text', [], 'contao_default'),
                    'help_headline' =>
                        $this->translator->trans('MSC.metamodels_support.help_headline', [], 'contao_default'),
                    'help_text' =>
                        $this->translator->trans('MSC.metamodels_support.help_text', [], 'contao_default'),
                    'github_contributors' => $this->getJsonFile($this->github),
                    'transifex_contributors' => $this->getJsonFile($this->transifex)
                ]
            )
        );
    }

    /**
     * Load the passed file and decode it.
     *
     * @param string $filename The file name.
     *
     * @return array
     */
    private function getJsonFile($filename)
    {
        if (!is_readable($filename)) {
            return [];
        }

        $contents = json_decode(file_get_contents($filename), true);

        return $contents ?: [];
    }
}


<?php

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

namespace MetaModels\BackendIntegration;

use Contao\Environment;

/**
 * Class ViewCombinations.
 *
 * Retrieve combinations of view and input screens for the currently logged in user (either frontend or backend).
 *
 * @deprecated This will get removed.
 */
class ViewCombinations extends \MetaModels\Helper\ViewCombinations
{
    /**
     * Authenticate the user preserving the object stack.
     *
     * @return bool
     */
    protected function authenticateUser()
    {
        if (\System::getContainer()->get('cca.dc-general.scope-matcher')->currentScopeIsUnknown()) {
            return false;
        }

        // Do not execute anything if we are on the login page because no User is logged in.
        if (strpos(Environment::get('script'), 'contao/login') !== false) {
            return false;
        }

        // Issue #66 - contao/install.php is not working anymore. Thanks to Stefan Lindecke (@lindesbs).
        if (strpos(Environment::get('request'), 'install') !== false) {
            return false;
        }

        if (strpos(Environment::get('script'), 'system/bin') !== false) {
            return false;
        }

        // Bug fix: If the user is not authenticated, contao will redirect to contao/index.php
        // But in this moment the TL_PATH is not defined, so the $this->Environment->request
        // generate a url without replacing the basepath(TL_PATH) with an empty string.
        $authResult = $this->getUser()->authenticate();

        return ($authResult === true || $authResult === null) ? true : false;
    }

    /**
     * {@inheritdoc}
     */
    protected function getUserGroups()
    {
        // Try to get the group(s)
        // there might be a NULL in there as BE admins have no groups and user might have one but it is not mandatory.
        // I would prefer a default group for both, fe and be groups.
        $groups = parent::getUserGroups();

        /** @noinspection PhpUndefinedFieldInspection */
        // Special case in combinations, admins have the implicit group id -1.
        if ($this->getUser()->admin) {
            $groups[] = -1;
        }

        return $groups;
    }
}


<?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     Christopher Boelter <christopher@boelter.eu>
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\DcGeneral\DataDefinition\Palette\Condition\Property;

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

/**
 * Condition checking that the value of a property is the same as a passed value.
 */
class PropertyContainAnyOfCondition implements PropertyConditionInterface
{
    /**
     * The property name.
     *
     * @var string
     */
    protected $propertyName;

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

    /**
     * Use strict compare mode.
     *
     * @var bool
     */
    protected $strict;

    /**
     * Create a new instance.
     *
     * @param string $propertyName  The name of the property.
     *
     * @param mixed  $propertyValue The value of the property to match.
     *
     * @param bool   $strict        Flag if the comparison shall be strict (type safe).
     */
    public function __construct($propertyName = '', $propertyValue = null, $strict = false)
    {
        $this->propertyName  = (string) $propertyName;
        $this->propertyValue = $propertyValue;
        $this->strict        = (bool) $strict;
    }

    /**
     * Set the property name.
     *
     * @param string $propertyName The property name.
     *
     * @return PropertyContainAnyOfCondition
     */
    public function setPropertyName($propertyName)
    {
        $this->propertyName = (string) $propertyName;
        return $this;
    }

    /**
     * Retrieve the property name.
     *
     * @return string
     */
    public function getPropertyName()
    {
        return $this->propertyName;
    }

    /**
     * Set the property value to match.
     *
     * @param mixed $propertyValue The value.
     *
     * @return PropertyContainAnyOfCondition
     */
    public function setPropertyValue($propertyValue)
    {
        $this->propertyValue = $propertyValue;
        return $this;
    }

    /**
     * Retrieve the property value to match.
     *
     * @return mixed
     */
    public function getPropertyValue()
    {
        return $this->propertyValue;
    }

    /**
     * Set the flag if the comparison shall be strict (type safe).
     *
     * @param boolean $strict The flag.
     *
     * @return PropertyContainAnyOfCondition
     */
    public function setStrict($strict)
    {
        $this->strict = (bool) $strict;
        return $this;
    }

    /**
     * Retrieve the flag if the comparison shall be strict (type safe).
     *
     * @return boolean
     *
     * @SuppressWarnings(PHPMD.BooleanGetMethodName)
     */
    public function getStrict()
    {
        return $this->strict;
    }

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

        if (!$values || !is_array($values)) {
            return false;
        }

        foreach ($values as $value) {
            if (in_array($value, $this->propertyValue, $this->strict)) {
                return true;
            }
        }

        return false;
    }

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


<?php

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

namespace MetaModels\DcGeneral\Events\MetaModel;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractContainerAwareEvent;
use MetaModels\BackendIntegration\InputScreen\IInputScreen;
use MetaModels\BackendIntegration\InputScreen\InputScreen;
use MetaModels\IMetaModel;

/**
 * This event is triggered to allow adding of model operations for MetaModels when the data container is being built.
 */
class BuildMetaModelOperationsEvent extends AbstractContainerAwareEvent
{
    /**
     * The event name.
     */
    const NAME = 'metamodels.dc-general.events.metamodel.build.metamodel.operations';

    /**
     * The MetaModel instance.
     *
     * @var IMetaModel
     */
    protected $metaModel;

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

    /**
     * Create a new container aware event.
     *
     * @param IMetaModel         $metaModel     The MetaModel.
     *
     * @param ContainerInterface $dataContainer The data container information.
     *
     * @param array              $inputScreen   The input screen in use.
     */
    public function __construct(
        IMetaModel $metaModel,
        ContainerInterface $dataContainer,
        array $inputScreen
    ) {
        parent::__construct($dataContainer);

        $this->metaModel   = $metaModel;
        $this->inputScreen = $inputScreen;
    }

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

    /**
     * Retrieve the input screen.
     *
     * @return array
     */
    public function getScreen()
    {
        return $this->inputScreen;
    }

    /**
     * Retrieve the input screen.
     *
     * @return IInputScreen
     *
     * @deprecated The InputScreen class will get removed.
     */
    public function getInputScreen()
    {
        return new InputScreen(
            \System::getContainer()->get('cca.legacy_dic')->getService('metamodels-service-container'),
            $this->inputScreen['meta'],
            $this->inputScreen['properties'],
            $this->inputScreen['conditions'],
            $this->inputScreen['groupSort']
        );
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractEnvironmentAwareEvent;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractModelAwareEvent;
use MetaModels\Attribute\IAttribute;
use MetaModels\Attribute\IAttributeFactory;
use MetaModels\IFactory;
use MetaModels\IMetaModel;

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

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

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

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

    /**
     * Get the MetaModel instance referenced in the pid property of the Model.
     *
     * @param ModelInterface $model The model.
     *
     * @return IMetaModel
     *
     * @throws \InvalidArgumentException When the MetaModel could not be retrieved.
     */
    protected function getMetaModelByModelPid(ModelInterface $model)
    {
        $metaModel = $this
            ->factory
            ->getMetaModel(
                $this->factory->translateIdToMetaModelName($model->getProperty('pid'))
            );

        if ($metaModel === null) {
            throw new \InvalidArgumentException('Could not retrieve MetaModel ' . $model->getProperty('pid'));
        }

        return $metaModel;
    }

    /**
     * Create an attribute from the passed data.
     *
     * @param ModelInterface|null $model The information.
     *
     * @return IAttribute|null
     *
     * @throws \InvalidArgumentException When the MetaModel could not be retrieved.
     */
    protected function createAttributeInstance(ModelInterface $model = null)
    {
        if (null === $model) {
            return null;
        }

        return $this->attributeFactory->createAttribute(
            $model->getPropertiesAsArray(),
            $this->getMetaModelByModelPid($model)
        );
    }

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

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

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

        return true;
    }
}


<?php

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

namespace MetaModels\BackendIntegration\InputScreen;

/**
 * Implementation of IInputScreenGroupingAndSorting.
 */
class InputScreenGroupingAndSorting implements IInputScreenGroupingAndSorting
{
    /**
     * The parenting input screen.
     *
     * @var IInputScreen
     */
    protected $inputScreen;

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

    /**
     * Create a new instance.
     *
     * @param array        $data        The information about the input screen.
     *
     * @param IInputScreen $inputScreen The information about all contained properties.
     */
    public function __construct($data, IInputScreen $inputScreen)
    {
        $this->data        = $data;
        $this->inputScreen = $inputScreen;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getRenderGroupType()
    {
        if ($this->isManualSorting()) {
            return 'none';
        }

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

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

    /**
     * {@inheritDoc}
     */
    public function getRenderGroupAttribute()
    {
        if (!empty($this->data['rendergroupattr'])) {
            $metaModel = $this->getMetaModel();
            if ($metaModel) {
                $attribute = $metaModel->getAttributeById($this->data['rendergroupattr']);
                if ($attribute) {
                    return $attribute->getColName();
                }
            }
        }

        return '';
    }

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

    /**
     * {@inheritDoc}
     */
    public function getRenderSortAttribute()
    {
        if (!empty($this->data['rendersortattr'])) {
            $metaModel = $this->getMetaModel();
            if ($metaModel) {
                $attribute = $metaModel->getAttributeById($this->data['rendersortattr']);
                if ($attribute) {
                    return $attribute->getColName();
                }
            }
        }

        return '';
    }

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

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

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


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Event\PreDeleteModelEvent;

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

        if ($attribute = $this->createAttributeInstance($event->getModel())) {
            $this->deleteConditionSettings($event);
            $attribute->destroyAUX();
        }
    }

    /**
     * Delete joint condition setting with attribute.
     *
     * @param PreDeleteModelEvent $event The event.
     *
     * @return void
     */
    protected function deleteConditionSettings(PreDeleteModelEvent $event)
    {
        $environment  = $event->getEnvironment();
        $model        = $event->getModel();
        $dataProvider = $environment->getDataProvider('tl_metamodel_dcasetting_condition');

        $conditions = $dataProvider->fetchAll(
            $dataProvider->getEmptyConfig()->setFilter(
                [['operation' => '=', 'property' => 'attr_id', 'value' => $model->getId()]]
            )
        );

        if ($conditions->count() < 1) {
            return;
        }

        $conditionsGeneral            = new \DC_General($dataProvider->getEmptyModel()->getProviderName());
        $conditionsEnvironment        = $conditionsGeneral->getEnvironment();
        $conditionsDataDefinition     = $conditionsEnvironment->getDataDefinition();
        $conditionsPalettesDefinition = $conditionsDataDefinition->getPalettesDefinition();

        /** @var \Iterator $conditionsIterator */
        $conditionsIterator = $conditions->getIterator();
        while ($currentCondition = $conditionsIterator->current()) {
            $conditionPalette    = $conditionsPalettesDefinition->getPaletteByName(
                $currentCondition->getProperty('type')
            );
            $conditionProperties = $conditionPalette->getVisibleProperties(
                $currentCondition
            );

            foreach ($conditionProperties as $conditionProperty) {
                if ($conditionProperty->getName() !== 'attr_id') {
                    continue;
                }

                $dataProvider->delete($currentCondition);
            }

            $conditionsIterator->next();
        }
    }
}


<?php

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

namespace MetaModels\CoreBundle\DcGeneral;

use MetaModels\IMetaModel;

/**
 * This is the abstract base for attribute aware condition factories.
 */
abstract class AbstractAttributeConditionFactory implements AttributeAwarePropertyConditionFactoryInterface
{
    /**
     * Extract the attribute instance from the MetaModel.
     *
     * @param IMetaModel $metaModel   The MetaModel instance.
     *
     * @param string     $attributeId The attribute id.
     *
     * @return string
     *
     * @throws \RuntimeException When the attribute could not be retrieved.
     */
    protected function attributeIdToName(IMetaModel $metaModel, $attributeId)
    {
        if (null === $attribute = $metaModel->getAttributeById($attributeId)) {
            throw new \RuntimeException(sprintf(
                'Could not retrieve attribute %s from MetaModel %s.',
                $attributeId,
                $metaModel->getTableName()
            ));
        }

        return $attribute->getColName();
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\DecodePropertyValueForWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractEnvironmentAwareEvent;

/**
 * This handles the rendering of models to labels.
 */
class AttributeIdListener extends AbstractConditionFactoryUsingListener
{
    /**
     * Prepares an option list with alias => name connection for all attributes.
     *
     * This is used in the attr_id select box.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     */
    public function getAttributeOptions(GetPropertyOptionsEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $result        = [];
        $metaModel     = $this->getMetaModel($event->getEnvironment());
        $conditionType = $event->getModel()->getProperty('type');
        foreach ($metaModel->getAttributes() as $attribute) {
            if (!$this->conditionFactory->supportsAttribute($conditionType, $attribute->get('type'))) {
                continue;
            }

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

        $event->setOptions($result);
    }

    /**
     * Translates an attribute id to a generated alias.
     *
     * @param DecodePropertyValueForWidgetEvent $event The event.
     *
     * @return void
     */
    public function decodeAttributeValue(DecodePropertyValueForWidgetEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $metaModel = $this->getMetaModel($event->getEnvironment());
        $value     = $event->getValue();

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

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

    /**
     * Translates an generated alias to the corresponding attribute id.
     *
     * @param EncodePropertyValueFromWidgetEvent $event The event.
     *
     * @return void
     */
    public function encodeAttributeValue(EncodePropertyValueFromWidgetEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $metaModel = $this->getMetaModel($event->getEnvironment());
        $value     = $event->getValue();

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

        // Cut off the 'mm_xyz_' prefix.
        $value = substr($value, \strlen($metaModel->getTableName() . '_'));

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

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

    /**
     * {@inheritDoc}
     */
    protected function wantToHandle(AbstractEnvironmentAwareEvent $event)
    {
        if (!parent::wantToHandle($event)) {
            return false;
        }
        if (method_exists($event, 'getPropertyName') && ('attr_id' !== $event->getPropertyName())) {
            return false;
        }
        if (method_exists($event, 'getProperty') && ('attr_id' !== $event->getProperty())) {
            return false;
        }

        return true;
    }
}


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\DefinitionBuilder;

use ContaoCommunityAlliance\DcGeneral\Contao\Dca\ContaoDataProviderInformation;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DataProviderDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefaultDataProviderDefinition;
use MetaModels\DcGeneral\Data\Driver;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\IFactory;
use MetaModels\ViewCombination\ViewCombination;

/**
 * This class takes care of populating the data provider instances.
 */
class DataProviderBuilder
{
    use MetaModelDefinitionBuilderTrait;

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

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

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

    /**
     * Create the data provider definition in the container if not already set.
     *
     * @param IMetaModelDataDefinition $container The data container.
     *
     * @return void
     */
    protected function build(IMetaModelDataDefinition $container)
    {
        $inputScreen = $this->viewCombination->getScreen($container->getName());
        if (!$inputScreen) {
            return;
        }
        $meta = $inputScreen['meta'];

        $config = $this->getDataProviderDefinition($container);

        // Check config if it already exists, if not, add it.
        if (!$config->hasInformation($container->getName())) {
            $providerInformation = new ContaoDataProviderInformation();
            $providerInformation->setName($container->getName());
            $config->addInformation($providerInformation);
        } else {
            $providerInformation = $config->getInformation($container->getName());
        }
        $basicDefinition = $container->getBasicDefinition();
        if ($providerInformation instanceof ContaoDataProviderInformation) {
            $providerInformation
                ->setTableName($container->getName())
                ->setClassName(Driver::class)
                ->setInitializationData(['source' => $container->getName()])
                ->setVersioningEnabled(false);
            $basicDefinition->setDataProvider($container->getName());
        }

        // If in hierarchical mode, set the root provider.
        if ($basicDefinition->getMode() == BasicDefinitionInterface::MODE_HIERARCHICAL) {
            $basicDefinition->setRootDataProvider($container->getName());
        }

        // If not standalone, set the correct parent provider.
        if ('ctable' === $meta['rendertype']) {
            $parentTable = $meta['ptable'];
            // Check config if it already exists, if not, add it.
            if (!$config->hasInformation($parentTable)) {
                $providerInformation = new ContaoDataProviderInformation();
                $providerInformation->setName($parentTable);
                $config->addInformation($providerInformation);
            } else {
                $providerInformation = $config->getInformation($parentTable);
            }

            if ($providerInformation instanceof ContaoDataProviderInformation) {
                $providerInformation
                    ->setTableName($parentTable)
                    ->setInitializationData(['source' => $parentTable]);
                // How can we honor other drivers? We do only check for MetaModels and legacy SQL here.
                if (in_array($parentTable, $this->factory->collectNames())) {
                    $providerInformation->setClassName(Driver::class);
                }

                $basicDefinition->setParentDataProvider($parentTable);
            }
        }
    }

    /**
     * Retrieve the data provider definition.
     *
     * @param IMetaModelDataDefinition $container The data container.
     *
     * @return DataProviderDefinitionInterface|DefaultDataProviderDefinition
     */
    private function getDataProviderDefinition(IMetaModelDataDefinition $container)
    {
        // Parse data provider.
        if ($container->hasDataProviderDefinition()) {
            return $container->getDataProviderDefinition();
        }

        $config = new DefaultDataProviderDefinition();
        $container->setDataProviderDefinition($config);
        return $config;
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetGlobalButtonEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use Doctrine\DBAL\Connection;
use MetaModels\IFactory;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * This fixes the href on the "add-all" button in input screens.
 */
class AddAllButtonListener
{
    /**
     * The database.
     *
     * @var Connection
     */
    private $connection;

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

    /**
     * The URL generator.
     *
     * @var UrlGeneratorInterface
     */
    private $urlGenerator;

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

    /**
     * Clear the button if the User is not admin.
     *
     * @param GetGlobalButtonEvent $event The event.
     *
     * @return void
     */
    public function getGlobalButton(GetGlobalButtonEvent $event)
    {
        $environment = $event->getEnvironment();
        if ('addall' !== $event->getKey()
        || 'tl_metamodel_rendersetting' !== $environment->getDataDefinition()->getName()) {
            return;
        }

        $renderSetting = ModelId::fromSerialized($environment->getInputProvider()->getParameter('pid'))->getId();

        $modelId = $this->connection->createQueryBuilder()
            ->select('r.pid')
            ->from('tl_metamodel_rendersettings', 'r')
            ->where('r.id=:pid')
            ->setParameter('pid', $renderSetting)
            ->execute()
            ->fetchColumn();

        $name = $this->factory->translateIdToMetaModelName($modelId);

        $event->setHref(
            $this->urlGenerator->generate(
                'metamodels.rendersetting.add_all',
                ['metaModel' => $name, 'renderSetting' => $renderSetting]
            )
        );
    }
}