<?php

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

namespace MetaModels\Filter\Setting;

use Contao\StringUtil;
use Contao\System;
use Contao\Widget;
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\Widget\GetAttributesFromDcaEvent;
use MetaModels\Filter\FilterUrl;
use MetaModels\Filter\FilterUrlBuilder;
use MetaModels\FrontendIntegration\FrontendFilterOptions;
use MetaModels\IItem;
use MetaModels\IMetaModelsServiceContainer;
use MetaModels\Render\Setting\ICollection as IRenderSettings;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Base class for filter setting implementation.
 */
abstract class Simple implements ISimple
{
    /**
     * The parenting filter setting container this setting belongs to.
     *
     * @var ICollection
     */
    private $collection = null;

    /**
     * The attributes of this filter setting.
     *
     * @var array
     */
    private $data = array();

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

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

    /**
     * Constructor - initialize the object and store the parameters.
     *
     * @param ICollection                   $collection       The parenting filter settings object.
     * @param array                         $data             The attributes for this filter setting.
     * @param EventDispatcherInterface|null $eventDispatcher  The event dispatcher.
     * @param FilterUrlBuilder|null         $filterUrlBuilder The filter URL builder.
     */
    public function __construct(
        $collection,
        $data,
        EventDispatcherInterface $eventDispatcher = null,
        FilterUrlBuilder $filterUrlBuilder = null
    ) {
        $this->collection = $collection;
        $this->data       = $data;

        if (null === $eventDispatcher) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Event dispatcher is not passed as constructor argument.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd

            $eventDispatcher = System::getContainer()->get('event_dispatcher');
        }

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

        $this->eventDispatcher  = $eventDispatcher;
        $this->filterUrlBuilder = $filterUrlBuilder;
    }

    /**
     * Retrieve the service container.
     *
     * @return IMetaModelsServiceContainer
     *
     * @deprecated Inject needed services via constructor or setter.
     */
    public function getServiceContainer()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated and will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd
        return $this->getMetaModel()->getServiceContainer();
    }

    /**
     * Retrieve the event dispatcher.
     *
     * @return EventDispatcherInterface
     *
     * @deprecated Inject the event dispatcher via constructor or setter.
     */
    public function getEventDispatcher()
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            '"' .__METHOD__ . '" is deprecated and will get removed.',
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        return $this->eventDispatcher;
    }

    /**
     * {@inheritdoc}
     */
    public function get($strKey)
    {
        return isset($this->data[$strKey]) ? $this->data[$strKey] : null;
    }

    /**
     * Get the parenting collection instance.
     *
     * @return ICollection The parent.
     */
    public function getFilterSettings()
    {
        return $this->collection;
    }

    /**
     * Return the MetaModel instance this filter setting relates to.
     *
     * @return \MetaModels\IMetaModel
     */
    public function getMetaModel()
    {
        return $this->getFilterSettings()->getMetaModel();
    }

    /**
     * Returns if the given value is currently active in the given filter settings.
     *
     * @param array  $arrWidget    The widget information.
     *
     * @param array  $arrFilterUrl The filter url parameters to use.
     *
     * @param string $strKeyOption The option value to determine.
     *
     * @return bool  true If the given value is mentioned in the given filter parameters, false otherwise.
     */
    protected function isActiveFrontendFilterValue($arrWidget, $arrFilterUrl, $strKeyOption)
    {
        // Special case, the "empty" value first.
        if (empty($strKeyOption) && !isset($arrFilterUrl[$arrWidget['eval']['urlparam']])) {
            return true;
        }
        $blnIsActive = isset($arrFilterUrl[$arrWidget['eval']['urlparam']])
            && ($arrFilterUrl[$arrWidget['eval']['urlparam']] == $strKeyOption);
        if (!$blnIsActive && $this->get('defaultid')) {
            $blnIsActive = ($arrFilterUrl[$arrWidget['eval']['urlparam']] == $this->get('defaultid'));
        }
        return $blnIsActive;
    }

    /**
     * Translate an option to a proper url value to be used in the filter url.
     *
     * Overriding this method allows to toggle the value in the url in addition to extract
     * or inject a value into an "combined" filter url parameter (like tags i.e.)
     *
     * @param array  $arrWidget    The widget information.
     *
     * @param array  $arrFilterUrl The filter url parameters to use.
     *
     * @param string $strKeyOption The option value to determine.
     *
     * @return string The filter url value to use for link gererating.
     */
    protected function getFrontendFilterValue($arrWidget, $arrFilterUrl, $strKeyOption)
    {
        // Toggle if active.
        if ($this->isActiveFrontendFilterValue($arrWidget, $arrFilterUrl, $strKeyOption)) {
            return '';
        }
        return $strKeyOption;
    }

    /**
     * Add a parameter to the url, if it is auto_item, it will get prepended.
     *
     * @param string $url   The url built so far.
     *
     * @param string $name  The parameter name.
     *
     * @param mixed  $value The parameter value.
     *
     * @return string.
     *
     * @deprecated Not in use anymore, use the FilterUrlBuilder.
     */
    protected function addUrlParameter($url, $name, $value)
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            sprintf('"%1$s" has been deprecated in favor of the "FilterUrlBuilder"', __METHOD__),
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

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

        $value = str_replace('%', '%%', urlencode($value));

        if (empty($value)) {
            return $url;
        }

        if ($name !== 'auto_item') {
            $url .= '/' . $name . '/' . $value;
        } else {
            $url = '/' . $value . $url;
        }

        return $url;
    }

    /**
     * Build the filter url based upon the fragments.
     *
     * @param array  $fragments The parameters to be used in the Url.
     *
     * @param string $searchKey The param key to handle for "this".
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     *
     * @deprecated Not in use anymore, use the FilterUrlBuilder.
     */
    protected function buildFilterUrl($fragments, $searchKey)
    {
        // @codingStandardsIgnoreStart
        @trigger_error(
            sprintf('"%1$s" has been deprecated in favor of the "FilterUrlBuilder"', __METHOD__),
            E_USER_DEPRECATED
        );
        // @codingStandardsIgnoreEnd

        $url   = '';
        $found = false;

        // Create base url containing for preserving the current filter on unrelated widgets and modules.
        // The URL parameter concerning us will be masked via %s to be used later on in a sprintf().
        foreach ($fragments as $key => $value) {
            // Skip the magic "language" parameter.
            if (($key == 'language') && $GLOBALS['TL_CONFIG']['addLanguageToUrl']) {
                continue;
            }

            if ($key == $searchKey) {
                if ($key !== 'auto_item') {
                    $url .= '%s';
                } else {
                    $url = '%s' . $url;
                }
                $found = true;
            } else {
                $url = $this->addUrlParameter($url, $key, $value);
            }
        }

        // If we have not found our parameter in the URL, we add it as %s now to be able to populate it via sprintf()
        // below.
        if (!$found) {
            if ($searchKey !== 'auto_item') {
                $url .= '%s';
            } else {
                $url = '%s' . $url;
            }
        }

        return $url;
    }

    /**
     * Generate the options for the frontend widget as the frontend templates expect them.
     *
     * The returning array will be made of option arrays containing the following fields:
     * * key    The option value as raw key from the options array in the given widget information.
     * * value  The value to show as option label.
     * * href   The URL to use to activate this value in the filter.
     * * active Boolean determining if this value is the current active option in the widget.
     * * class  The CSS class to use. Contains active if the option is active or is empty otherwise.
     *
     * @param array $arrWidget     The widget information to use for value generating.
     *
     * @param array $arrFilterUrl  The filter url parameters to use.
     *
     * @param array $arrJumpTo     The jumpTo page to use for URL generating - if empty, the current
     *                             frontend page will get used.
     *
     * @param bool  $blnAutoSubmit Determines if the generated options/widgets shall perform auto submitting
     *                             or not.
     *
     * @return array The filter option values to use in the mm_filteritem_* templates.
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    protected function prepareFrontendFilterOptions($arrWidget, $arrFilterUrl, $arrJumpTo, $blnAutoSubmit)
    {
        $arrOptions = array();
        if (!isset($arrWidget['options'])) {
            return $arrOptions;
        }

        $filterUrl = new FilterUrl($arrJumpTo);
        foreach ($arrFilterUrl as $name => $value) {
            if (is_array($value)) {
                $value = implode(',', array_filter($value));
            }

            $filterUrl->setSlug($name, (string) $value);
        }
        $parameterName = $arrWidget['eval']['urlparam'];

        if ($arrWidget['eval']['includeBlankOption']) {
            $blnActive = $this->isActiveFrontendFilterValue($arrWidget, $arrFilterUrl, '');

            $arrOptions[] = array
            (
                'key'    => '',
                'value'  => (
                $arrWidget['eval']['blankOptionLabel']
                    ? $arrWidget['eval']['blankOptionLabel']
                    : $GLOBALS['TL_LANG']['metamodels_frontendfilter']['do_not_filter']
                ),
                'href'   => $this->filterUrlBuilder->generate($filterUrl->clone()->setSlug($parameterName, '')),
                'active' => $blnActive,
                'class'  => 'doNotFilter'.($blnActive ? ' active' : ''),
            );
        }

        foreach ($arrWidget['options'] as $strKeyOption => $strOption) {
            $strValue  = $this->getFrontendFilterValue($arrWidget, $arrFilterUrl, $strKeyOption);
            $blnActive = $this->isActiveFrontendFilterValue($arrWidget, $arrFilterUrl, $strKeyOption);

            $arrOptions[] = array
            (
                'key'    => $strKeyOption,
                'value'  => $strOption,
                'href'   => $this->filterUrlBuilder->generate($filterUrl->clone()->setSlug($parameterName, $strValue)),
                'active' => $blnActive,
                'class'  => StringUtil::standardize($strKeyOption) . ($blnActive ? ' active' : '')
            );
        }
        return $arrOptions;
    }

    /**
     * Returns the frontend filter widget information for the filter setting.
     *
     * The returning array will hold the following keys:
     * * class      - The CSS classes for the widget.
     * * label      - The label text for the widget.
     * * formfield  - The parsed default widget object for this filter setting.
     * * raw        - The widget information that was used for rendering "formfield" as raw array (this means
     *                prior calling prepareForWidget()).
     * * urlparam   - The URL parameter used for this widget.
     * * options    - The filter options available to be used in selects etc. see prepareFrontendFilterOptions
     *                for details on the contained array.
     * * autosubmit - True if the frontend filter shall perform auto form submitting, false otherwise.
     * * urlvalue   - The current value selected in the filtersetting. Will use "urlvalue" from $arrWidget with
     *                fallback to the value of the url param in the filter url.
     *
     * @param array                 $arrWidget                The widget information to use for generating.
     *
     * @param array                 $arrFilterUrl             The filter url parameters to use.
     *
     * @param array                 $arrJumpTo                The jumpTo page to use for URL generating - if empty, the
     *                                                        current frontend page will get used.
     *
     * @param FrontendFilterOptions $objFrontendFilterOptions The options to use.
     *
     * @return array
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    protected function prepareFrontendFilterWidget(
        $arrWidget,
        $arrFilterUrl,
        $arrJumpTo,
        FrontendFilterOptions $objFrontendFilterOptions
    ) {
        $strClass = $GLOBALS['TL_FFL'][$arrWidget['inputType']];

        // No widget? no output! that's it.
        if (!$strClass) {
            return array();
        }

        // Determine current value.
        $arrWidget['value'] = isset($arrFilterUrl[$arrWidget['eval']['urlparam']])
            ? $arrFilterUrl[$arrWidget['eval']['urlparam']]
            : null;

        $event = new GetAttributesFromDcaEvent(
            $arrWidget,
            $arrWidget['eval']['urlparam']
        );

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

        if ($objFrontendFilterOptions->isAutoSubmit() && TL_MODE == 'FE') {
            $GLOBALS['TL_JAVASCRIPT']['metamodels'] = 'bundles/metamodelscore/js/metamodels.js';
        }

        /** @var \Widget $objWidget */
        $objWidget = new $strClass($event->getResult());
        $this->validateWidget($objWidget, $arrWidget['value']);
        $strField = $objWidget->generateWithError();

        return array
        (
            'class'      => sprintf(
                'mm_%s %s%s%s',
                $arrWidget['inputType'],
                $arrWidget['eval']['urlparam'],
                (($arrWidget['value'] !== null) ? ' used' : ' unused'),
                ($objFrontendFilterOptions->isAutoSubmit() ? ' submitonchange' : '')
            ),
            'label'      => $objWidget->generateLabel(),
            'formfield'  => $strField,
            'raw'        => $arrWidget,
            'urlparam'   => $arrWidget['eval']['urlparam'],
            'options'    => $this->prepareFrontendFilterOptions(
                $arrWidget,
                $arrFilterUrl,
                $arrJumpTo,
                $objFrontendFilterOptions->isAutoSubmit()
            ),
            'count'      => isset($arrWidget['count']) ? $arrWidget['count'] : null,
            'showCount'  => $objFrontendFilterOptions->isShowCountValues(),
            'autosubmit' => $objFrontendFilterOptions->isAutoSubmit(),
            'urlvalue'   => array_key_exists('urlvalue', $arrWidget) ? $arrWidget['urlvalue'] : $arrWidget['value'],
            'errors'     => $objWidget->hasErrors() ? $objWidget->getErrors() : array()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function generateFilterUrlFrom(IItem $objItem, IRenderSettings $objRenderSetting)
    {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    public function getParameters()
    {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterDCA()
    {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterFilterNames()
    {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterFilterWidgets(
        $arrIds,
        $arrFilterUrl,
        $arrJumpTo,
        FrontendFilterOptions $objFrontendFilterOptions
    ) {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    public function getReferencedAttributes()
    {
        return array();
    }

    /**
     * Validate the widget using the value.
     *
     * @param Widget      $widget The widget to validate.
     *
     * @param string|null $value  The value to validate.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    protected function validateWidget($widget, $value)
    {
        if (null === $value) {
            return;
        }
        $widget->setInputCallback(function () use ($value) {
            return $value;
        });
        $widget->validate();
    }
}


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

namespace MetaModels\Attribute;

use MetaModels\IMetaModel;
use MetaModels\Render\Setting\ISimple as ISimpleRenderSetting;
use MetaModels\Render\Setting\Simple;
use MetaModels\Render\Template;

/**
 * This is the main MetaModels-attribute base class.
 * To create a MetaModelAttribute instance, use the {@link MetaModelAttributeFactory}
 * This class is the reference implementation for {@link IMetaModelAttribute}.
 *
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
abstract class Base implements IAttribute
{
    /**
     * The MetaModel instance this object belongs to.
     *
     * @var IMetaModel
     */
    private $metaModel;

    /**
     * The meta information of this attribute.
     *
     * @var array
     */
    protected $arrData = array();

    /**
     * Instantiate an MetaModel attribute.
     *
     * Note that you should not use this directly but use the factory classes to instantiate attributes.
     *
     * @param IMetaModel $objMetaModel The MetaModel instance this attribute belongs to.
     *
     * @param array      $arrData      The information array, for attribute information, refer to documentation of
     *                                 table tl_metamodel_attribute and documentation of the certain attribute classes
     *                                 for information what values are understood.
     */
    public function __construct(IMetaModel $objMetaModel, $arrData = array())
    {
        // Meta information.
        foreach (array_intersect($this->getAttributeSettingNames(), array_keys($arrData)) as $strSettingName) {
            $this->set($strSettingName, $arrData[$strSettingName]);
        }
        $this->metaModel = $objMetaModel;
    }

    /**
     * Helper method for generating a list of "?" to be used in a Contao query.
     *
     * Usage:
     *  $params = array('1', '2', 'potential SQL injection string');
     *  $result = $database
     *       ->prepare('SELECT * FROM tl_something WHERE id IN (' . $this->parameterMask($params) . ')')
     *       ->execute($params);
     *
     * @param array $parameters The parameters to be used in a query.
     *
     * @return string
     */
    protected function parameterMask($parameters)
    {
        return implode(',', array_fill(0, count($parameters), '?'));
    }

    /**
     * Retrieve the human readable name (or title) from the attribute.
     *
     * If the MetaModel is translated, the currently active language is used,
     * with properly falling back to the defined fallback language.
     *
     * @return string the human readable name
     */
    public function getName()
    {
        if (is_array($this->arrData['name'])) {
            return $this->getLangValue($this->get('name')) ?: $this->getColName();
        }
        return $this->arrData['name'] ?: $this->getColName();
    }

    /**
     * This extracts the value for the given language from the given language array.
     *
     * If the language is not contained within the value array, the fallback language from the parenting
     * {@link IMetaModel} instance is tried as well.
     *
     * @param array  $arrValues   The array holding all language values in the form array('langcode' => $varValue).
     *
     * @param string $strLangCode The language code of the language to fetch. Optional, if not given,
     *                            $GLOBALS['TL_LANGUAGE'] is used.
     *
     * @return mixed|null the value for the given language or the fallback language, NULL if neither is present.
     */
    protected function getLangValue($arrValues, $strLangCode = null)
    {
        if (!($this->getMetaModel()->isTranslated() && is_array($arrValues))) {
            return $arrValues;
        }

        if ($strLangCode === null) {
            return $this->getLangValue($arrValues, $this->getMetaModel()->getActiveLanguage());
        }

        if (array_key_exists($strLangCode, $arrValues)) {
            return $arrValues[$strLangCode];
        }

        // Language code not set, use fallback.
        return $arrValues[$this->getMetaModel()->getFallbackLanguage()];
    }

    /**
     * Hook additional attribute formatter that want to format the value.
     *
     * @param array   $arrBaseFormatted The current result array. The keys "raw" and "text" are always populated.
     *
     * @param array   $arrRowData       The Raw values from the database.
     *
     * @param string  $strOutputFormat  The output format to use.
     *
     * @param ISimpleRenderSetting $objSettings      The output format settings.
     *
     * @return mixed
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    public function hookAdditionalFormatters($arrBaseFormatted, $arrRowData, $strOutputFormat, $objSettings)
    {
        $arrResult = $arrBaseFormatted;

        if (isset($GLOBALS['METAMODEL_HOOKS']['parseValue']) && is_array($GLOBALS['METAMODEL_HOOKS']['parseValue'])) {
            foreach ($GLOBALS['METAMODEL_HOOKS']['parseValue'] as $callback) {
                list($strClass, $strMethod) = $callback;

                $objCallback = (in_array('getInstance', get_class_methods($strClass)))
                    ? call_user_func(array($strClass, 'getInstance'))
                    : new $strClass();

                $arrResult = $objCallback->$strMethod(
                    $this,
                    $arrResult,
                    $arrRowData,
                    $strOutputFormat,
                    $objSettings
                );
            }
        }

        return $arrResult;
    }

    /**
     * When rendered via a template, this populates the template with values.
     *
     * @param Template $objTemplate The Template instance to populate.
     *
     * @param array    $arrRowData  The row data for the current item.
     *
     * @param ISimpleRenderSetting  $objSettings The render settings to use for this attribute.
     *
     * @return void
     */
    protected function prepareTemplate(Template $objTemplate, $arrRowData, $objSettings)
    {
        $objTemplate->setData(array(
            'attribute'        => $this,
            'settings'         => $objSettings,
            'row'              => $arrRowData,
            'raw'              => $arrRowData[$this->getColName()],
            'additional_class' => $objSettings->get('additional_class')
                ? ' ' . $objSettings->get('additional_class')
                : ''
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getColName()
    {
        return $this->arrData['colname'];
    }

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

    /**
     * {@inheritdoc}
     */
    public function get($strKey)
    {
        return isset($this->arrData[$strKey]) ? $this->arrData[$strKey] : null;
    }

    /**
     * {@inheritdoc}
     */
    public function set($strKey, $varValue)
    {
        if (in_array($strKey, $this->getAttributeSettingNames())) {
            if (!is_array($varValue) && (substr($varValue, 0, 2) == 'a:')) {
                $unSerialized = unserialize($varValue);
            }

            if (isset($unSerialized) && is_array($unSerialized)) {
                $this->arrData[$strKey] = $unSerialized;
            } else {
                $this->arrData[$strKey] = $varValue;
            }
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function handleMetaChange($strMetaName, $varNewValue)
    {
        // By default we accept any change of meta information.
        $this->set($strMetaName, $varNewValue);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function destroyAUX()
    {
        // No-op.
    }

    /**
     * {@inheritdoc}
     */
    public function initializeAUX()
    {
        // No-op.
    }

    /**
     * {@inheritdoc}
     */
    public function getAttributeSettingNames()
    {
        return array(
            // Settings originating from tl_metamodel_attribute.
            'id', 'pid', 'tstamp', 'name', 'description', 'type', 'colname', 'isvariant',
            // Settings originating from tl_metamodel_dcasetting.
            'tl_class', 'readonly');
    }

    /**
     * Set the language strings.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function setLanguageStrings()
    {
        // Only overwrite the language if not already set.
        if (empty($GLOBALS['TL_LANG'][$this->getMetaModel()->getTableName()][$this->getColName()])) {
            $GLOBALS['TL_LANG'][$this->getMetaModel()->getTableName()][$this->getColName()] = array
            (
                $this->getLangValue($this->get('name')),
                $this->getLangValue($this->get('description')),
            );
        }
    }

    /**
     * Retrieve the base definition by the user from dca_config.
     *
     * @return array
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getBaseDefinition()
    {
        $this->setLanguageStrings();
        $tableName  = $this->getMetaModel()->getTableName();
        $definition = array();
        if (isset($GLOBALS['TL_DCA'][$tableName]['fields'][$this->getColName()])) {
            $definition = $GLOBALS['TL_DCA'][$tableName]['fields'][$this->getColName()];
        }

        return array_replace_recursive(
            array
            (
                'label' => &$GLOBALS['TL_LANG'][$tableName][$this->getColName()],
                'eval'  => array()
            ),
            $definition
        );
    }

    /**
     * Check if a value may be overridden.
     *
     * @param string $name The name of the value.
     *
     * @return bool
     */
    private function isAllowedValue($name)
    {
        // Load the allowed overrides only once.
        $allowedSettings = array_flip($this->getAttributeSettingNames());

        return isset($allowedSettings[$name]);
    }

    /**
     * Extract an override value.
     *
     * @param string $name      The name of the value.
     *
     * @param array  $overrides The overrides containing the values to be overridden.
     *
     * @return mixed
     */
    protected function getOverrideValue($name, $overrides)
    {
        if ($this->isAllowedValue($name) && isset($overrides[$name])) {
            return $overrides[$name];
        }

        return $this->get($name);
    }

    /**
     * Extract an override value.
     *
     * @param array $fieldDefinition The field definition.
     *
     * @param array $overrides       The overrides containing the values to be overridden.
     *
     * @return array
     */
    private function setBaseEval($fieldDefinition, $overrides)
    {
        if ($this->isAllowedValue('isunique')) {
            $fieldDefinition['eval']['unique'] = (bool) $this->getOverrideValue('isunique', $overrides);
        }

        foreach (array(
            'tl_class',
            'mandatory',
            'alwaysSave',
            'chosen',
            'allowHtml',
            'preserveTags',
            'decodeEntities',
            'rte',
            'rows',
            'cols',
            'spaceToUnderscore',
            'includeBlankOption',
            'submitOnChange',
            'readonly'
        ) as $name) {
            if (empty($fieldDefinition['eval'][$name])
                && ($value = $this->getOverrideValue($name, $overrides))
            ) {
                $fieldDefinition['eval'][$name] = $value;
            }
        }

        // If we have unique, enforce mandatory.
        if (!empty($fieldDefinition['eval']['unique'])) {
            $fieldDefinition['eval']['mandatory'] = true;
        }

        return $fieldDefinition;
    }

    /**
     * {@inheritdoc}
     */
    public function getFieldDefinition($arrOverrides = array())
    {
        $arrFieldDef = $this->setBaseEval($this->getBaseDefinition(), $arrOverrides);

        if ($this->isAllowedValue('trailingSlash')) {
            $trailingSlash = $this->getOverrideValue('trailingSlash', $arrOverrides);
            if ($trailingSlash != 2) {
                $arrFieldDef['eval']['trailingSlash'] = (bool) $arrOverrides['trailingSlash'];
            }
        }

        // Panel conditions.
        if ($this->isAllowedValue('filterable')) {
            $arrFieldDef['filter'] = (bool) $this->getOverrideValue('filterable', $arrOverrides);
        }
        if ($this->isAllowedValue('searchable')) {
            $arrFieldDef['search'] = (bool) $this->getOverrideValue('searchable', $arrOverrides);
        }

        return $arrFieldDef;
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     *
     * @deprecated Use DataDefinition builders in DC_General 2.0.0
     */
    public function getItemDCA($arrOverrides = array())
    {
        $arrReturn = array
        (
            'fields' => array_merge(
                array($this->getColName() => $this->getFieldDefinition($arrOverrides)),
                (array) $GLOBALS['TL_DCA'][$this->getMetaModel()->getTableName()]['fields'][$this->getColName()]
            ),
        );

        return $arrReturn;
    }

    /**
     * {@inheritdoc}
     */
    public function valueToWidget($varValue)
    {
        return $varValue;
    }

    /**
     * {@inheritdoc}
     */
    public function widgetToValue($varValue, $itemId)
    {
        return $varValue;
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultRenderSettings()
    {
        $objSetting = new Simple(
            array
            (
                'template' => 'mm_attr_' . $this->get('type')
            )
        );
        return $objSetting;
    }

    /**
     * {@inheritdoc}
     */
    public function parseValue($arrRowData, $strOutputFormat = 'text', $objSettings = null)
    {
        $arrResult = array(
            'raw' => $arrRowData[$this->getColName()],
        );

        /** @var ISimpleRenderSetting $objSettings */
        if ($objSettings && $objSettings->get('template')) {
            $strTemplate = $objSettings->get('template');

            $objTemplate = new Template($strTemplate);

            $this->prepareTemplate($objTemplate, $arrRowData, $objSettings);

            // Now the desired format.
            if ($strValue = $objTemplate->parse($strOutputFormat, false)) {
                $arrResult[$strOutputFormat] = $strValue;
            }

            // Text rendering is mandatory, try with the current setting,
            // upon exception, try again with the default settings, as the template name might have changed.
            // if this fails again, we are definately out of luck and bail the exception.
            try {
                $arrResult['text'] = $objTemplate->parse('text', true);
            } catch (\Exception $e) {
                $objSettingsFallback = $this->getDefaultRenderSettings()->setParent($objSettings->getParent());

                $objTemplate = new Template($objSettingsFallback->get('template'));
                $this->prepareTemplate($objTemplate, $arrRowData, $objSettingsFallback);

                $arrResult['text'] = $objTemplate->parse('text', true);
            }
        } else {
            // Text rendering is mandatory, therefore render using default render settings.
            $arrResult = $this->parseValue($arrRowData, 'text', $this->getDefaultRenderSettings());
        }

        // HOOK: apply additional formatters to attribute.
        $arrResult = $this->hookAdditionalFormatters($arrResult, $arrRowData, $strOutputFormat, $objSettings);

        return $arrResult;
    }

    /**
     * {@inheritdoc}
     *
     * This base implementation returns the value itself.
     */
    public function getFilterUrlValue($varValue)
    {
        // We are parsing as text here as this was the way before this method was implemented. See #216.
        $arrResult = $this->parseValue(array($this->getColName() => $varValue), 'text');

        return $arrResult['text'];
    }

    /**
     * {@inheritdoc}
     */
    public function sortIds($idList, $strDirection)
    {
        // Base implementation, do not perform any sorting.
        return $idList;
    }

    /**
     * {@inheritdoc}
     * Base implementation, do not perform any search;
     */
    public function searchFor($strPattern)
    {
        return array();
    }

    /**
     * {@inheritdoc}
     *
     * Base implementation, return empty array.
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function filterGreaterThan($varValue, $blnInclusive = false)
    {
        return array();
    }

    /**
     * {@inheritdoc}
     *
     * Base implementation, return empty array.
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function filterLessThan($varValue, $blnInclusive = false)
    {
        return array();
    }

    /**
     * {@inheritdoc}
     * Base implementation, merge the result of filterLessThan() and filterGreaterThan().
     */
    public function filterNotEqual($varValue)
    {
        return array_merge($this->filterLessThan($varValue), $this->filterGreaterThan($varValue));
    }

    /**
     * {@inheritdoc}
     * Base implementation, do not perform anything.
     */
    public function modelSaved($objItem)
    {
        // No-op.
    }
}


<?php

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

namespace MetaModels\CoreBundle\EventListener\DcGeneral\DefinitionBuilder;

use Contao\StringUtil;
use ContaoCommunityAlliance\DcGeneral\Contao\DataDefinition\Definition\Contao2BackendViewDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\DefaultFilterElementInformation;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\DefaultLimitElementInformation;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\DefaultSearchElementInformation;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\DefaultSortElementInformation;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\DefaultSubmitElementInformation;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\SearchElementInformationInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\Panel\SubmitElementInformationInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\PanelRowCollectionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\View\PanelRowInterface;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\ViewCombination\ViewCombination;

/**
 * This class handles the panel building.
 */
class PanelBuilder
{
    use MetaModelDefinitionBuilderTrait;

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

    /**
     * The input screen to use (only set during build phase).
     *
     * @var array
     */
    private $inputScreen;

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

    /**
     * Build the data definition.
     *
     * @param IMetaModelDataDefinition $container The container to populate.
     *
     * @return void
     */
    protected function build(IMetaModelDataDefinition $container)
    {
        $this->inputScreen = $this->viewCombination->getScreen($container->getName());

        // Check if we have a BackendViewDef.
        if ($container->hasDefinition(Contao2BackendViewDefinitionInterface::NAME)) {
            /** @var Contao2BackendViewDefinitionInterface $view */
            $view = $container->getDefinition(Contao2BackendViewDefinitionInterface::NAME);
        } else {
            return;
        }

        // Get the panel layout.
        $panelLayout = $this->inputScreen['meta']['panelLayout'];

        // Check if we have a layout.
        if (empty($panelLayout)) {
            return;
        }

        // Get the layout from the dca.
        $arrRows = StringUtil::trimsplit(';', $panelLayout);

        // Create a new panel container.
        $panel     = $view->getPanelLayout();
        $panelRows = $panel->getRows();

        foreach ($arrRows as $rowNo => $rowElements) {
            // Get the row, if we have one or create a new one.
            if ($panelRows->getRowCount() < ($rowNo + 1)) {
                $panelRow = $panelRows->addRow();
            } else {
                $panelRow = $panelRows->getRow($rowNo);
            }

            // Get the fields.
            $fields = StringUtil::trimsplit(',', $rowElements);
            $fields = array_reverse($fields);

            $this->parsePanelRow($fields, $panelRow);

            // If we have no entries for this row, remove it.
            if ($panelRow->getCount() == 0) {
                $panelRows->deleteRow($rowNo);
            }
        }

        $this->ensureSubmitElement($panelRows);
        $this->inputScreen = null;
    }

    /**
     * Ensure at least one submit element is present in any of the rows.
     *
     * If no submit element is present, this method will create one at the end of the last row.
     *
     * @param PanelRowCollectionInterface $panelRows The panel rows.
     *
     * @return void
     */
    private function ensureSubmitElement($panelRows)
    {
        // Check if we have a submit button.
        $hasSubmit = false;
        foreach ($panelRows as $panelRow) {
            foreach ($panelRow as $element) {
                if ($element instanceof SubmitElementInformationInterface) {
                    $hasSubmit = true;
                    break;
                }

                if ($hasSubmit) {
                    break;
                }
            }
        }

        // If not add a submit.
        if (!$hasSubmit && $panelRows->getRowCount()) {
            $row = $panelRows->getRow($panelRows->getRowCount() - 1);
            $row->addElement(new DefaultSubmitElementInformation(), 0);
        }
    }

    /**
     * Parse a single row with all elements.
     *
     * @param array             $fields   A list of fields for adding to the row.
     *
     * @param PanelRowInterface $panelRow The row container itself.
     *
     * @return void
     */
    private function parsePanelRow($fields, PanelRowInterface $panelRow)
    {
        // Parse each type.
        foreach ($fields as $field) {
            switch ($field) {
                case 'sort':
                    $this->parsePanelSort($panelRow);
                    break;

                case 'limit':
                    $this->parsePanelLimit($panelRow);
                    break;

                case 'filter':
                    $this->parsePanelFilter($panelRow);
                    break;

                case 'search':
                    $this->parsePanelSearch($panelRow);
                    break;

                case 'submit':
                    $this->parsePanelSubmit($panelRow);
                    break;

                default:
                    break;
            }
        }
    }

    /**
     * Add filter elements to the panel.
     *
     * @param PanelRowInterface $row The row to which the element shall get added to.
     *
     * @return void
     */
    private function parsePanelFilter(PanelRowInterface $row)
    {
        foreach ($this->inputScreen['properties'] as $value) {
            if (!empty($value['filter'])) {
                $element = new DefaultFilterElementInformation();
                $element->setPropertyName($value['col_name']);
                if (!$row->hasElement($element->getName())) {
                    $row->addElement($element);
                }
            }
        }
    }

    /**
     * Add sort element to the panel.
     *
     * @param PanelRowInterface $row The row to which the element shall get added to.
     *
     * @return void
     */
    private function parsePanelSort(PanelRowInterface $row)
    {
        if (!$row->hasElement('sort')) {
            $element = new DefaultSortElementInformation();
            $row->addElement($element);
        }
    }

    /**
     * Add search element to the panel.
     *
     * @param PanelRowInterface $row The row to which the element shall get added to.
     *
     * @return void
     *
     * @throws \InvalidArgumentException When the search element does not implement the correct interface.
     */
    private function parsePanelSearch(PanelRowInterface $row)
    {
        if ($row->hasElement('search')) {
            $element = $row->getElement('search');
        } else {
            $element = new DefaultSearchElementInformation();
        }

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

        foreach ($this->inputScreen['properties'] as $value) {
            if (!empty($value['search'])) {
                $element->addProperty($value['col_name']);
            }
        }

        if ($element->getPropertyNames() && !$row->hasElement('search')) {
            $row->addElement($element);
        }
    }

    /**
     * Add  elements to the panel.
     *
     * @param PanelRowInterface $row The row to which the element shall get added to.
     *
     * @return void
     */
    private function parsePanelLimit(PanelRowInterface $row)
    {
        if (!$row->hasElement('limit')) {
            $row->addElement(new DefaultLimitElementInformation());
        }
    }

    /**
     * Add  elements to the panel.
     *
     * @param PanelRowInterface $row The row to which the element shall get added to.
     *
     * @return void
     */
    private function parsePanelSubmit(PanelRowInterface $row)
    {
        if (!$row->hasElement('submit')) {
            $row->addElement(new DefaultSubmitElementInformation());
        }
    }
}


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

/**
 * Upgrade handler class that changes structural changes in the database.
 * This should rarely be necessary but sometimes we need it.
 */
class UpgradeHandler
{
    /**
     * Retrieve the database instance from Contao.
     *
     * @return \Database
     *
     * @SuppressWarnings(PHPMD.ShortMethodName)
     * @SuppressWarnings(PHPMD.CamelCaseMethodName)
     */
    protected static function DB()
    {
        return \Database::getInstance();
    }

    /**
     * Handle database upgrade for the jumpTo field.
     *
     * Introduced: pre release 1.0.
     *
     * If the field 'metamodel_jumpTo' does exist in tl_module or tl_content,
     * it will get created and the content from jumpTo will get copied over.
     *
     * @return void
     */
    protected static function upgradeJumpTo()
    {
        $objDB = self::DB();
        if ($objDB->tableExists('tl_content', null, true)
            && !$objDB->fieldExists('metamodel_jumpTo', 'tl_content', true)
        ) {
            // Create the column in the database and copy the data over.
            TableManipulation::createColumn(
                'tl_content',
                'metamodel_jumpTo',
                'int(10) unsigned NOT NULL default \'0\''
            );
            if ($objDB->fieldExists('jumpTo', 'tl_content', true)) {
                $objDB->execute('UPDATE tl_content SET metamodel_jumpTo=jumpTo;');
            }
        }

        if ($objDB->tableExists('tl_module', null, true)
            && !$objDB->fieldExists('metamodel_jumpTo', 'tl_module', true)
        ) {
            // Create the column in the database and copy the data over.
            TableManipulation::createColumn(
                'tl_module',
                'metamodel_jumpTo',
                'int(10) unsigned NOT NULL default \'0\''
            );
            if ($objDB->fieldExists('jumpTo', 'tl_module', true)) {
                $objDB->execute('UPDATE tl_module SET metamodel_jumpTo=jumpTo;');
            }
        }
    }

    /**
     * Handle database upgrade for the published field in tl_metamodel_dcasetting.
     *
     * Introduced: version 1.0.1
     *
     * If the field 'published' does not exist in tl_metamodel_dcasetting,
     * it will get created and all rows within that table will get initialized to 1
     * to have the prior behaviour back (everything was being published before then).
     *
     * @return void
     */
    protected static function upgradeDcaSettingsPublished()
    {
        $objDB = self::DB();
        if ($objDB->tableExists('tl_metamodel_dcasetting', null, true)
            && !$objDB->fieldExists('published', 'tl_metamodel_dcasetting', true)
        ) {
            // Create the column in the database and copy the data over.
            TableManipulation::createColumn(
                'tl_metamodel_dcasetting',
                'published',
                'char(1) NOT NULL default \'\''
            );
            // Publish everything we had so far.
            $objDB->execute('UPDATE tl_metamodel_dcasetting SET published=1;');
        }
    }

    /**
     * Handle database upgrade for changing sub palettes to input field conditions.
     *
     * @return void
     */
    protected static function changeSubPalettesToConditions()
    {
        $objDB = self::DB();

        // Create the table.
        if (!$objDB->tableExists('tl_metamodel_dcasetting_condition')) {
            $objDB->execute(
                'CREATE TABLE `tl_metamodel_dcasetting_condition` (
                `id` int(10) unsigned NOT NULL auto_increment,
                `pid` int(10) unsigned NOT NULL default \'0\',
                `settingId` int(10) unsigned NOT NULL default \'0\',
                `sorting` int(10) unsigned NOT NULL default \'0\',
                `tstamp` int(10) unsigned NOT NULL default \'0\',
                `enabled` char(1) NOT NULL default \'\',
                `type` varchar(255) NOT NULL default \'\',
                `attr_id` int(10) unsigned NOT NULL default \'0\',
                `comment` varchar(255) NOT NULL default \'\',
                `value` blob NULL,
                PRIMARY KEY  (`id`)
                )ENGINE=MyISAM DEFAULT CHARSET=utf8;'
            );
        }

        if ($objDB->tableExists('tl_metamodel_dcasetting', null, true)
            && $objDB->fieldExists('subpalette', 'tl_metamodel_dcasetting', true)
        ) {
            $subpalettes = $objDB->execute('SELECT * FROM tl_metamodel_dcasetting WHERE subpalette!=0');

            if ($subpalettes->numRows) {
                // Get all attribute names and setting ids.
                $attributes = $objDB
                    ->execute('
                        SELECT attr_id, colName
                        FROM tl_metamodel_dcasetting AS setting
                        LEFT JOIN tl_metamodel_attribute AS attribute
                        ON (setting.attr_id=attribute.id)
                        WHERE dcatype=\'attribute\'
                    ');

                $attr = array();
                while ($attributes->next()) {
                    $attr[$attributes->attr_id] = $attributes->colName;
                }

                $checkboxes = $objDB->execute('
                    SELECT *
                    FROM tl_metamodel_dcasetting
                    WHERE
                        subpalette=0
                        AND dcatype=\'attribute\'
                    ');

                $check = array();
                while ($checkboxes->next()) {
                    $check[$checkboxes->id] = $checkboxes->attr_id;
                }

                while ($subpalettes->next()) {
                    // Add property value condition for parent property dependency.
                    $data = array(
                        'pid' => 0,
                        'settingId' => $subpalettes->id,
                        'sorting' => '128',
                        'tstamp' => time(),
                        'enabled' => '1',
                        'type' => 'conditionpropertyvalueis',
                        'attr_id' => $check[$subpalettes->subpalette],
                        'comment' => sprintf(
                            'Only show when checkbox "%s" is checked',
                            $attr[$check[$subpalettes->subpalette]]
                        ),
                        'value' => '1',
                    );

                    $objDB
                        ->prepare('INSERT INTO tl_metamodel_dcasetting_condition %s')
                        ->set($data)
                        ->execute();

                    $objDB
                        ->prepare('UPDATE tl_metamodel_dcasetting SET subpalette=0 WHERE id=?')
                        ->execute($subpalettes->id);

                    $objDB
                        ->prepare('UPDATE tl_metamodel_dcasetting SET submitOnChange=1 WHERE id=?')
                        ->execute($subpalettes->subpalette);
                }
            }

            TableManipulation::dropColumn('tl_metamodel_dcasetting', 'subpalette', true);
        }
    }

    /**
     * Upgrade the database to change from closed dca to editable, creatable and deletable.
     *
     * @return void
     */
    protected static function upgradeClosed()
    {
        $objDB = self::DB();

        // Change isclosed to iseditable, iscreatable and isdeleteable.
        if ($objDB->tableExists('tl_metamodel_dca', null, true)
            && !$objDB->fieldExists('iseditable', 'tl_metamodel_dca')) {
            // Create the column in the database and copy the data over.
            TableManipulation::createColumn(
                'tl_metamodel_dca',
                'iseditable',
                'char(1) NOT NULL default \'\''
            );
            TableManipulation::createColumn(
                'tl_metamodel_dca',
                'iscreatable',
                'char(1) NOT NULL default \'\''
            );
            TableManipulation::createColumn(
                'tl_metamodel_dca',
                'isdeleteable',
                'char(1) NOT NULL default \'\''
            );

            $objDB->execute('
                UPDATE tl_metamodel_dca
                SET
                    iseditable=isclosed^1,
                    iscreatable=isclosed^1,
                    isdeleteable=isclosed^1
            ');

            TableManipulation::dropColumn('tl_metamodel_dca', 'isclosed', true);
        }
    }

    /**
     * Upgrade the input screens.
     *
     * @return void
     */
    protected static function upgradeInputScreenMode()
    {
        $objDB = self::DB();
        if (!$objDB->tableExists('tl_metamodel_dca', null, true)) {
            return;
        }

        if (!$objDB->fieldExists('mode', 'tl_metamodel_dca')) {
            return;
        }

        // Create the fields for grouping and sorting and migrate.
        if (!$objDB->fieldExists('rendermode', 'tl_metamodel_dca')) {
            TableManipulation::createColumn(
                'tl_metamodel_dca',
                'rendermode',
                'varchar(12) NOT NULL default \'\''
            );
        }

        $objDB->execute('UPDATE tl_metamodel_dca SET rendermode="flat" WHERE mode IN (0,1,2,3)');
        $objDB->execute('UPDATE tl_metamodel_dca SET rendermode="parented" WHERE mode IN (4)');
        $objDB->execute('UPDATE tl_metamodel_dca SET rendermode="hierarchical" WHERE mode IN (5,6)');

        TableManipulation::dropColumn('tl_metamodel_dca', 'mode', true);
    }

    /**
     * Upgrade the input screens.
     *
     * @return void
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    protected static function upgradeInputScreenFlag()
    {
        $objDB = self::DB();
        if (!$objDB->tableExists('tl_metamodel_dca', null, true)) {
            return;
        }
        if (!$objDB->fieldExists('flag', 'tl_metamodel_dca')) {
            return;
        }

        if (!$objDB->tableExists('tl_metamodel_dca_sortgroup', null, true)) {
            $objDB->execute('
                CREATE TABLE `tl_metamodel_dca_sortgroup` (
                `id` int(10) unsigned NOT NULL auto_increment,
                `pid` int(10) unsigned NOT NULL default \'0\',
                `sorting` int(10) unsigned NOT NULL default \'0\',
                `tstamp` int(10) unsigned NOT NULL default \'0\',
                `name` text NULL,
                `isdefault` char(1) NOT NULL default \'\',
                `ismanualsort` char(1) NOT NULL default \'\',
                `rendergrouptype` varchar(10) NOT NULL default \'none\',
                `rendergrouplen` int(10) unsigned NOT NULL default \'1\',
                `rendergroupattr` int(10) unsigned NOT NULL default \'0\',
                `rendersort` varchar(10) NOT NULL default \'asc\',
                `rendersortattr` int(10) unsigned NOT NULL default \'0\',
                PRIMARY KEY  (`id`)
                ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
            ');
        }

        $dca = $objDB->execute('SELECT * FROM tl_metamodel_dca');

        while ($dca->next()) {
            $renderGroupLen = 0;
            if (in_array($dca->flag, array(1,2,3,4))) {
                $renderGroupType = 'char';
                if (in_array($dca->flag, array(1,2))) {
                    $renderGroupLen = 1;
                } else {
                    $renderGroupLen = 2;
                }
            } elseif (in_array($dca->flag, array(5,6))) {
                $renderGroupType = 'day';
            } elseif (in_array($dca->flag, array(7,8))) {
                $renderGroupType = 'month';
            } elseif (in_array($dca->flag, array(9,10))) {
                $renderGroupType = 'year';
            } elseif (in_array($dca->flag, array(11,12))) {
                $renderGroupType = 'digit';
            } else {
                $renderGroupType = 'none';
            }

            $data = array(
                'pid'             => $dca->id,
                'sorting'         => 128,
                'tstamp'          => time(),
                'name'            => null,
                'isdefault'       => '1',
                'ismanualsort'    => '1',
                'rendergrouptype' => $renderGroupType,
                'rendergrouplen'  => $renderGroupLen,
                'rendergroupattr' => 0,
                'rendersort'      => in_array($dca->flag, array(2,4,6,8,10,12)) ? 'desc' : 'asc',
                'rendersortattr'  => 0,
            );

            $objDB
                ->prepare('INSERT INTO tl_metamodel_dca_sortgroup %s')
                ->set($data)
                ->execute();
        }

        TableManipulation::dropColumn('tl_metamodel_dca', 'flag', true);
    }

    /**
     * Perform all upgrade steps.
     *
     * @return void
     */
    public static function perform()
    {
        self::upgradeJumpTo();
        self::upgradeDcaSettingsPublished();
        self::changeSubPalettesToConditions();
        self::upgradeClosed();
        self::upgradeInputScreenMode();
        self::upgradeInputScreenFlag();
    }
}


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

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent;
use MetaModels\Helper\TableManipulator;

/**
 * This prefixes all tables with "mm_".
 */
class TableNamePrefixingListener extends AbstractAbstainingListener
{
    /**
     * The table manipulator.
     *
     * @var TableManipulator
     */
    private $tableManipulator;

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

    /**
     * Called by tl_metamodel.tableName onsave_callback.
     *
     * Prefixes the table name with mm_ if not provided by the user as such.
     * Checks if the table name is legal to the DB.
     *
     * @param EncodePropertyValueFromWidgetEvent $event The event.
     *
     * @return void
     *
     * @throws \RuntimeException On invalid table names.
     */
    public function handle(EncodePropertyValueFromWidgetEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getProperty() !== 'tableName')) {
            return;
        }

        // See #49.
        $tableName = strtolower($event->getValue());

        if (!strlen($tableName)) {
            throw new \RuntimeException('Table name not given');
        }

        // Force mm_ prefix.
        if (substr($tableName, 0, 3) !== 'mm_') {
            $tableName = 'mm_' . $tableName;
        }

        $dataProvider = $event->getEnvironment()->getDataProvider('tl_metamodel');

        try {
            // New model, ensure the table does not exist.
            if (!$event->getModel()->getId()) {
                $this->tableManipulator->checkTableDoesNotExist($tableName);
            } else {
                // Edited model, ensure the value is unique and then that the table does not exist.
                $oldVersion = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($event->getModel()->getId()));

                if ($oldVersion->getProperty('tableName') !== $event->getModel()->getProperty('tableName')) {
                    $this->tableManipulator->checkTableDoesNotExist($tableName);
                }
            }
        } catch (\Exception $exception) {
            throw new \RuntimeException($exception->getMessage(), $exception->getCode(), $exception);
        }

        $event->setValue($tableName);
    }
}


<?php

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

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

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use ContaoCommunityAlliance\DcGeneral\Event\AbstractEnvironmentAwareEvent;
use MetaModels\Attribute\IInternal;

/**
 * This provides the attribute name options.
 */
class AttributeOptionsListener extends AbstractListener
{
    /**
     * Provide options for attribute type selection.
     *
     * @param GetPropertyOptionsEvent $event The event.
     *
     * @return void
     */
    public function handle(GetPropertyOptionsEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $result    = [];
        $metaModel = $this->getMetaModelFromModel($event->getModel());

        foreach ($metaModel->getAttributes() as $attribute) {
            if ($attribute instanceof IInternal) {
                continue;
            }
            $typeName                      = $attribute->get('type');
            $result[$attribute->get('id')] = $attribute->getName() . ' [' . $typeName . ']';
        }

        $event->setOptions($result);
    }

    /**
     * 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)
    {
        /** @var GetPropertyOptionsEvent $event */
        return parent::wantToHandle($event)
            && in_array($event->getPropertyName(), ['rendergroupattr', 'rendersortattr']);
    }
}


<?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     Tim Gatzky <info@tim-gatzky.de>
 * @author     Martin Treml <github@r2pi.net>
 * @author     Jeremie Constant <j.constant@imi.de>
 * @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 Contao\StringUtil;
use MetaModels\Events\RenderItemListEvent;
use MetaModels\Filter\Setting\IFilterSettingFactory;
use MetaModels\Helper\PaginationLimitCalculator;
use MetaModels\Render\Setting\IRenderSettingFactory;
use MetaModels\Render\Template;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Implementation of a general purpose MetaModel listing.
 *
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
 */
class ItemList implements IServiceContainerAware
{
    /**
     * Sort by attribute.
     *
     * @var string
     */
    protected $strSortBy = '';

    /**
     * Sort by attribute.
     *
     * @var string
     */
    protected $strSortDirection = 'ASC';

    /**
     * The view to use.
     *
     * @var int
     */
    protected $intView = 0;

    /**
     * Sort by attribute.
     *
     * @var string
     */
    protected $strOutputFormat;

    /**
     * The MetaModel to use.
     *
     * @var int
     */
    protected $intMetaModel = 0;

    /**
     * The filter to use.
     *
     * @var int
     */
    protected $intFilter = 0;

    /**
     * The parameters for the filter.
     *
     * @var string[]
     */
    protected $arrParam = array();

    /**
     * The name of the attribute for the title.
     *
     * @var string
     */
    protected $strTitleAttribute = '';

    /**
     * The name of the attribute for the description.
     *
     * @var string
     */
    protected $strDescriptionAttribute = '';

    /**
     * The service container.
     *
     * @var IMetaModelsServiceContainer|\Closure
     *
     * @deprecated The service container will get removed.
     */
    private $serviceContainer;

    /**
     * The calculator for pagination and limit.
     *
     * @var PaginationLimitCalculator
     */
    private $paginationLimitCalculator;

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

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

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

    /**
     * The render setting factory.
     *
     * @var IRenderSettingFactory
     */
    private $renderSettingFactory;

    /**
     * Create a new instance.
     */
    public function __construct()
    {
        $this->paginationLimitCalculator = new PaginationLimitCalculator();
    }

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

        return $this;
    }

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

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

    /**
     * Set the factory.
     *
     * @param IFactory $factory The factory.
     *
     * @return ItemList
     */
    public function setFactory(IFactory $factory)
    {
        $this->factory = $factory;

        return $this;
    }

    /**
     * Retrieve the filter setting factory.
     *
     * @return IFactory
     */
    private function getFactory()
    {
        if ($this->factory) {
            return $this->factory;
        }

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

    /**
     * Set the render setting factory.
     *
     * @param IRenderSettingFactory $factory The factory.
     *
     * @return ItemList
     */
    public function setRenderSettingFactory(IRenderSettingFactory $factory)
    {
        $this->renderSettingFactory = $factory;

        return $this;
    }

    /**
     * Set eventDispatcher.
     *
     * @param EventDispatcherInterface $eventDispatcher The new value.
     *
     * @return ItemList
     */
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;

        return $this;
    }

    /**
     * Retrieve eventDispatcher.
     *
     * @return EventDispatcherInterface
     */
    private function getEventDispatcher()
    {
        if ($this->eventDispatcher) {
            return $this->eventDispatcher;
        }

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

    /**
     * Set the service container to use.
     *
     * @param IMetaModelsServiceContainer $serviceContainer The service container.
     *
     * @return ItemList
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function setServiceContainer(IMetaModelsServiceContainer $serviceContainer)
    {
        $this->serviceContainer = $serviceContainer;

        return $this;
    }

    /**
     * Set the service container to use (fallback for MM 2.0 compatibility).
     *
     * @param \Closure $serviceContainer The service container retriever.
     *
     * @return ItemList
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function setServiceContainerFallback($serviceContainer)
    {
        $this->serviceContainer = $serviceContainer;

        return $this;
    }

    /**
     * Try to set the default service container.
     *
     * @return void
     *
     * @throws \RuntimeException When the service container could not be set.
     *
     * @@SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function useDefaultServiceContainer()
    {
        $this->serviceContainer = $GLOBALS['container']['metamodels-service-container'];

        if (!($this->serviceContainer instanceof IMetaModelsServiceContainer)) {
            throw new \RuntimeException('Unable to retrieve default service container.');
        }
    }

    /**
     * Retrieve the service container in use.
     *
     * @return IMetaModelsServiceContainer|null
     *
     * @deprecated The service container will get removed, use the symfony service container instead.
     */
    public function getServiceContainer()
    {
        if (!$this->serviceContainer) {
            $this->useDefaultServiceContainer();
        }
        if (is_callable($this->serviceContainer)) {
            return $this->serviceContainer = $this->serviceContainer->__invoke();
        }

        return $this->serviceContainer;
    }

    /**
     * Set the limit.
     *
     * @param bool $blnUse    If true, use limit, if false no limit is applied.
     *
     * @param int  $intOffset Like in SQL, first element to be returned (0 based).
     *
     * @param int  $intLimit  Like in SQL, amount of elements to retrieve.
     *
     * @return ItemList
     */
    public function setLimit($blnUse, $intOffset, $intLimit)
    {
        $this
            ->paginationLimitCalculator
            ->setApplyLimitAndOffset($blnUse)
            ->setOffset($intOffset)
            ->setLimit($intLimit);

        return $this;
    }

    /**
     * Set page breaking to the given amount of items. A value of 0 disables pagination at all.
     *
     * @param int $intLimit The amount of items per page. A value of 0 disables pagination.
     *
     * @return ItemList
     */
    public function setPageBreak($intLimit)
    {
        $this->paginationLimitCalculator->setPerPage($intLimit);

        return $this;
    }

    /**
     * Set sorting to an attribute or system column optionally in the given direction.
     *
     * @param string $strSortBy    The name of the attribute or system column to be used for sorting.
     *
     * @param string $strDirection The direction, either ASC or DESC (optional).
     *
     * @return ItemList
     */
    public function setSorting($strSortBy, $strDirection = 'ASC')
    {
        $this->strSortBy        = $strSortBy;
        $this->strSortDirection = ($strDirection == 'DESC') ? 'DESC' : 'ASC';

        return $this;
    }

    /**
     * Override the output format of the used view.
     *
     * @param string|null $strOutputFormat The desired output format.
     *
     * @return ItemList
     */
    public function overrideOutputFormat($strOutputFormat = null)
    {
        $strOutputFormat = strval($strOutputFormat);
        if (strlen($strOutputFormat)) {
            $this->strOutputFormat = $strOutputFormat;
        } else {
            unset($this->strOutputFormat);
        }

        return $this;
    }

    /**
     * Set MetaModel and render settings.
     *
     * @param int $intMetaModel The MetaModel to use.
     *
     * @param int $intView      The render settings to use (if 0, the default will be used).
     *
     * @return ItemList
     */
    public function setMetaModel($intMetaModel, $intView)
    {
        $this->intMetaModel = $intMetaModel;
        $this->intView      = $intView;

        $this->prepareMetaModel();

        $this->prepareView();

        return $this;
    }

    /**
     * Add the attribute names for meta title and description.
     *
     * @param string $strTitleAttribute       Name of attribute for title.
     *
     * @param string $strDescriptionAttribute Name of attribue for description.
     *
     * @return ItemList
     */
    public function setMetaTags($strTitleAttribute, $strDescriptionAttribute)
    {
        $this->strDescriptionAttribute = $strDescriptionAttribute;
        $this->strTitleAttribute       = $strTitleAttribute;

        return $this;
    }

    /**
     * The Metamodel to use.
     *
     * @var IMetaModel
     */
    protected $objMetaModel;

    /**
     * The render settings to use.
     *
     * @var \MetaModels\Render\Setting\ICollection
     */
    protected $objView;

    /**
     * The render template to use.
     *
     * @var Template
     */
    protected $objTemplate;

    /**
     * The filter settings to use.
     *
     * @var \MetaModels\Filter\Setting\ICollection
     */
    protected $objFilterSettings;

    /**
     * The filter to use.
     *
     * @var \MetaModels\Filter\IFilter
     */
    protected $objFilter;

    /**
     * Prepare the MetaModel.
     *
     * @return void
     *
     * @throws \RuntimeException When the MetaModel can not be found.
     */
    protected function prepareMetaModel()
    {
        $factory            = $this->getFactory();
        $this->objMetaModel = $factory->getMetaModel($factory->translateIdToMetaModelName($this->intMetaModel));
        if (!$this->objMetaModel) {
            throw new \RuntimeException('Could get metamodel id: ' . $this->intMetaModel);
        }
    }

    /**
     * Prepare the view.
     *
     * NOTE: must be called after prepareMetaModel().
     *
     * @return void
     */
    protected function prepareView()
    {
        if ($this->renderSettingFactory) {
            $this->objView = $this->renderSettingFactory->createCollection($this->objMetaModel, $this->intView);
        } else {
            $this->objView = $this->objMetaModel->getView($this->intView);
        }

        if ($this->objView) {
            $this->objTemplate       = new Template($this->objView->get('template'));
            $this->objTemplate->view = $this->objView;
        } else {
            // Fallback to default.
            $this->objTemplate = new Template('metamodel_full');
        }
    }

    /**
     * Set the filter setting to use.
     *
     * @param int $intFilter The filter setting to use.
     *
     * @return $this
     *
     * @throws \RuntimeException When the filter settings can not be found.
     */
    public function setFilterSettings($intFilter)
    {
        $this->intFilter = $intFilter;

        $this->objFilterSettings = $this->getFilterFactory()->createCollection($this->intFilter);

        if (!$this->objFilterSettings) {
            throw new \RuntimeException('Error: no filter object defined.');
        }

        return $this;
    }

    /**
     * Set parameters.
     *
     * @param string[] $arrPresets The parameter preset values to use.
     *
     * @param string[] $arrValues  The dynamic parameter values that may be used.
     *
     * @return ItemList
     *
     * @throws \RuntimeException When no filter settings have been set.
     */
    public function setFilterParameters($arrPresets, $arrValues)
    {
        if (!$this->objFilterSettings) {
            throw new \RuntimeException(
                'Error: no filter object defined, call setFilterSettings() before setFilterParameters().'
            );
        }

        $arrPresetNames    = $this->objFilterSettings->getParameters();
        $arrFEFilterParams = array_keys($this->objFilterSettings->getParameterFilterNames());

        $arrProcessed = array();

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

        // Now we have to use all FE filter params, that are either:
        // * not contained within the presets
        // * or are overridable.
        foreach ($arrFEFilterParams as $strParameter) {
            // Unknown parameter? - next please.
            if (!array_key_exists($strParameter, $arrValues)) {
                continue;
            }

            // Not a preset or allowed to override? - use value.
            if ((!array_key_exists($strParameter, $arrPresets)) || $arrPresets[$strParameter]['use_get']) {
                $arrProcessed[$strParameter] = $arrValues[$strParameter];
            }
        }

        $this->arrParam = $arrProcessed;

        return $this;
    }

    /**
     * Return the filter.
     *
     * @return \MetaModels\Filter\IFilter
     */
    public function getFilter()
    {
        return $this->objFilter;
    }

    /**
     * Return the filter settings.
     *
     * @return \MetaModels\Filter\Setting\ICollection
     */
    public function getFilterSettings()
    {
        return $this->objFilterSettings;
    }

    /**
     * The items in the list view.
     *
     * @var \MetaModels\IItems
     */
    protected $objItems = null;

    /**
     * Add additional filter rules to the list.
     *
     * Can be overridden by subclasses to add additional filter rules to the filter before it will get evaluated.
     *
     * @return ItemList
     */
    protected function modifyFilter()
    {
        return $this;
    }

    /**
     * Add additional filter rules to the list on the fly.
     *
     * @param \MetaModels\Filter\IFilterRule $objFilterRule The filter rule to add.
     *
     * @return ItemList
     */
    public function addFilterRule($objFilterRule)
    {
        if (!$this->objFilter) {
            $this->objFilter = $this->objMetaModel->getEmptyFilter();
        }

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

        return $this;
    }

    /**
     * Return all attributes that shall be fetched from the MetaModel.
     *
     * In this base implementation, this only includes the attributes mentioned in the render setting.
     *
     * @return string[] the names of the attributes to be fetched.
     */
    protected function getAttributeNames()
    {
        $arrAttributes = $this->objView->getSettingNames();

        // Get the right jumpTo.
        $strDesiredLanguage  = $this->getMetaModel()->getActiveLanguage();
        $strFallbackLanguage = $this->getMetaModel()->getFallbackLanguage();

        $intFilterSettings = 0;

        foreach ((array) $this->getView()->get('jumpTo') as $arrJumpTo) {
            // If either desired language or fallback, keep the result.
            if (!$this->getMetaModel()->isTranslated()
                || $arrJumpTo['langcode'] == $strDesiredLanguage
                || $arrJumpTo['langcode'] == $strFallbackLanguage) {
                $intFilterSettings = $arrJumpTo['filter'];
                // If the desired language, break. Otherwise try to get the desired one until all have been evaluated.
                if ($strDesiredLanguage == $arrJumpTo['langcode']) {
                    break;
                }
            }
        }

        if ($intFilterSettings) {
            $objFilterSettings = $this->getFilterFactory()->createCollection($intFilterSettings);
            $arrAttributes     = array_merge($objFilterSettings->getReferencedAttributes(), $arrAttributes);
        }

        return $arrAttributes;
    }

    /**
     * Prepare the rendering.
     *
     * @return ItemList
     */
    public function prepare()
    {
        if ($this->objItems) {
            return $this;
        }

        // Create an empty filter object if not done before.
        if (!$this->objFilter) {
            $this->objFilter = $this->objMetaModel->getEmptyFilter();
        }

        if ($this->objFilterSettings) {
            $this->objFilterSettings->addRules($this->objFilter, $this->arrParam);
        }

        $this->modifyFilter();

        $intTotal = $this->objMetaModel->getCount($this->objFilter);

        $calculator = $this->paginationLimitCalculator;
        $calculator->setTotalAmount($intTotal);
        $curPage = (int) \Input::get('page');
        if ($curPage > 1) {
            $calculator->setCurrentPage($curPage);
        }
        $this->objTemplate->total = $intTotal;

        $this->objItems = $this->objMetaModel->findByFilter(
            $this->objFilter,
            $this->strSortBy,
            $calculator->getCalculatedOffset(),
            $calculator->getCalculatedLimit(),
            $this->strSortDirection,
            $this->getAttributeNames()
        );

        return $this;
    }

    /**
     * Returns the pagination string.
     *
     * Remember to call prepare() first.
     *
     * @return string
     */
    public function getPagination()
    {
        return $this->paginationLimitCalculator->getPaginationString();
    }

    /**
     * Returns the item list in the view.
     *
     * @return IItems
     */
    public function getItems()
    {
        return $this->objItems;
    }

    /**
     * Returns the view.
     *
     * @return \MetaModels\Render\Setting\ICollection
     */
    public function getView()
    {
        return $this->objView;
    }

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

    /**
     * Retrieve the page object.
     *
     * @return object
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getPage()
    {
        return (TL_MODE == 'FE' && is_object($GLOBALS['objPage'])) ? $GLOBALS['objPage'] : null;
    }

    /**
     * Retrieve the output format used by this list.
     *
     * @return string
     */
    public function getOutputFormat()
    {
        if (isset($this->strOutputFormat)) {
            return $this->strOutputFormat;
        }

        if (isset($this->objView) && $this->objView->get('format')) {
            return $this->objView->get('format');
        }

        $page = $this->getPage();

        if ($page && $page->outputFormat) {
            return $page->outputFormat;
        }

        return 'text';
    }

    /**
     * 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];
    }

    /**
     * Retrieve the caption text for "No items found" message.
     *
     * 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>]['noItemsMsg']
     * 2. $GLOBALS['TL_LANG']['MSC'][<mm tablename>]['noItemsMsg']
     * 3. $GLOBALS['TL_LANG']['MSC']['noItemsMsg']
     *
     * @return string
     */
    protected function getNoItemsCaption()
    {
        return $this->getCaptionText('noItemsMsg');
    }

    /**
     * Set the title and description in the page object.
     *
     * @return void
     */
    private function setTitleAndDescription()
    {
        $page = $this->getPage();
        if ($page && $this->objItems->getCount()) {
            // Add title if needed.
            if (!empty($this->strTitleAttribute)) {
                while ($this->objItems->next()) {
                    /** @var IItem $objCurrentItem */
                    $objCurrentItem = $this->objItems->current();
                    $arrTitle       = $objCurrentItem->parseAttribute(
                        $this->strTitleAttribute,
                        'text',
                        $this->getView()
                    );

                    if (!empty($arrTitle['text'])) {
                        $page->pageTitle = strip_tags($arrTitle['text']);
                        break;
                    }
                }

                $this->objItems->reset();
            }

            // Add description if needed.
            if (!empty($this->strDescriptionAttribute)) {
                while ($this->objItems->next()) {
                    $objCurrentItem = $this->objItems->current();
                    $arrDescription = $objCurrentItem->parseAttribute(
                        $this->strDescriptionAttribute,
                        'text',
                        $this->getView()
                    );

                    if (!empty($arrDescription['text'])) {
                        $page->description = StringUtil::substr($arrDescription['text'], 160);
                        break;
                    }
                }

                $this->objItems->reset();
            }
        }
    }

    /**
     * Render the list view.
     *
     * @param bool   $blnNoNativeParsing Flag determining if the parsing shall be done internal or if the template will
     *                                   handle the parsing on it's own.
     *
     * @param object $objCaller          The object calling us, might be a Module or ContentElement or anything else.
     *
     * @return string
     */
    public function render($blnNoNativeParsing, $objCaller)
    {
        $event = new RenderItemListEvent($this, $this->objTemplate, $objCaller);
        $this->getEventDispatcher()->dispatch(MetaModelsEvents::RENDER_ITEM_LIST, $event);

        $this->objTemplate->noItemsMsg = $this->getNoItemsCaption();
        $this->objTemplate->details    = $this->getCaptionText('details');

        $this->prepare();
        $strOutputFormat = $this->getOutputFormat();

        if ($this->objItems->getCount() && !$blnNoNativeParsing) {
            $this->objTemplate->data = $this->objItems->parseAll($strOutputFormat, $this->objView);
        } else {
            $this->objTemplate->data = array();
        }

        $this->setTitleAndDescription();

        $this->objTemplate->caller       = $objCaller;
        $this->objTemplate->items        = $this->objItems;
        $this->objTemplate->filterParams = $this->arrParam;

        return $this->objTemplate->parse($strOutputFormat);
    }
}


<?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\Definition\BasicDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\DefaultBasicDefinition;
use MetaModels\DcGeneral\DataDefinition\IMetaModelDataDefinition;
use MetaModels\IFactory;
use MetaModels\ViewCombination\ViewCombination;

/**
 * This class handles building of the basic definition
 */
class BasicDefinitionBuilder
{
    use MetaModelDefinitionBuilderTrait;

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

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

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

    /**
     * Build the definition.
     *
     * @param IMetaModelDataDefinition $container The container being built.
     *
     * @return void
     */
    protected function build(IMetaModelDataDefinition $container)
    {
        $inputScreen = $this->viewCombination->getScreen($container->getName());
        if (!$inputScreen) {
            return;
        }
        $meta = $inputScreen['meta'];

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

        $config->setDataProvider($container->getName());

        $metaModel = $this->factory->getMetaModel($container->getName());
        // If we have variants, override all modes to tree mode.
        if ($metaModel->hasVariants()) {
            $config->setMode(BasicDefinitionInterface::MODE_HIERARCHICAL);
        }

        if ('hierarchical' === $meta['rendermode']) {
            // Hierarchical mode - Records are displayed as tree (see site structure).
            $config->setMode(BasicDefinitionInterface::MODE_HIERARCHICAL);
        } elseif ('parented' === $meta['rendermode']) {
            // Displays the child records of a parent record (see style sheets module).
            $config->setMode(BasicDefinitionInterface::MODE_PARENTEDLIST);
        } elseif ('flat' === $meta['rendermode']) {
            // Flat mode.
            $config->setMode(BasicDefinitionInterface::MODE_FLAT);
        }

        $config
            ->setEditable((bool) $meta['iseditable'])
            ->setCreatable((bool) $meta['iscreatable'])
            ->setDeletable((bool) $meta['isdeleteable']);

        if ($metaModel->hasVariants()) {
            ConditionBuilderWithVariants::calculateConditions($container, $inputScreen);
            return;
        }
        ConditionBuilderWithoutVariants::calculateConditions($container, $inputScreen);
    }

    /**
     * Get or create the basic definition.
     *
     * @param IMetaModelDataDefinition $container The definition being built.
     *
     * @return BasicDefinitionInterface
     */
    private function getOrCreateBasicDefinition(IMetaModelDataDefinition $container)
    {
        if ($container->hasBasicDefinition()) {
            return $container->getBasicDefinition();
        }
        $config = new DefaultBasicDefinition();
        $container->setBasicDefinition($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\DcaSetting;

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ManipulateWidgetEvent;
use MetaModels\CoreBundle\Assets\IconBuilder;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * This handles the attaching of the wizard for class selection.
 */
class TlClassWizardListener extends AbstractAbstainingListener
{
    /**
     * The icon builder.
     *
     * @var IconBuilder
     */
    private $iconBuilder;

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

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param IconBuilder              $iconBuilder       The icon builder.
     * @param UrlGeneratorInterface    $urlGenerator      The URL generator.
     */
    public function __construct(
        RequestScopeDeterminator $scopeDeterminator,
        IconBuilder $iconBuilder,
        UrlGeneratorInterface $urlGenerator
    ) {
        parent::__construct($scopeDeterminator);

        $this->iconBuilder  = $iconBuilder;
        $this->urlGenerator = $urlGenerator;
    }

    /**
     * Calculate the wizard.
     *
     * @param ManipulateWidgetEvent $event The event.
     *
     * @return void
     */
    public function handle(ManipulateWidgetEvent $event)
    {
        if (!($this->wantToHandle($event) && ('tl_class' === $event->getProperty()->getName()))) {
            return;
        }

        $link = ' %4$s';

        $image = $this->iconBuilder->getBackendIconImageTag(
            'bundles/metamodelscore/images/icons/dca_wizard.png',
            $event->getEnvironment()->getTranslator()->translate('stylepicker', 'tl_metamodel_dca'),
            'style="vertical-align:top;"'
        );

        $event->getWidget()->wizard = sprintf(
            $link,
            $this->urlGenerator->generate('metamodels.picker', [
                'tbl' => $event->getEnvironment()->getDataDefinition()->getName(),
                'fld' => $event->getProperty()->getName(),
                'inputName' => 'ctrl_' . $event->getProperty()->getName(),
                'id' => $event->getModel()->getId(),
                'item' => 'PALETTE_STYLE_PICKER',
            ]),
            addslashes($event->getEnvironment()->getTranslator()->translate('stylepicker', 'tl_metamodel_dca')),
            $event->getModel()->getId(),
            $image
        );
    }
}