<?php

/**
 * This file is part of contao-community-alliance/dc-general-contao-frontend.
 *
 * (c) 2015-2017 Contao Community Alliance.
 *
 * 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    contao-community-alliance/dc-general-contao-frontend
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Richard Henkenjohann <richardhenkenjohann@googlemail.com>
 * @copyright  2015-2017 Contao Community Alliance.
 * @license    https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0
 * @filesource
 */

namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View;

use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
use ContaoCommunityAlliance\Contao\Bindings\Events\Widget\GetAttributesFromDcaEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\Compatibility\DcCompat;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\DecodePropertyValueForWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ManipulateWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\BuildWidgetEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\Properties\PropertyInterface;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;

/**
 * Widget Builder to build Contao frontend widgets.
 */
class DefaultWidgetBuilder
{

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

    /**
     * DefaultWidgetBuilder constructor.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The request scope determinator.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator)
    {
        $this->scopeDeterminator = $scopeDeterminator;
    }

    /**
     * Handle the build widget event.
     *
     * @param BuildWidgetEvent $event The event.
     *
     * @return void
     */
    public function handleEvent(BuildWidgetEvent $event)
    {
        // Only run in the frontend or when the widget is not build yet
        if (false === $this->scopeDeterminator->currentScopeIsFrontend() || null !== $event->getWidget()) {
            return;
        }

        $widget = $this->buildWidget($event->getEnvironment(), $event->getProperty(), $event->getModel());

        $event->setWidget($widget);
    }

    /**
     * Build a widget for a given property.
     *
     * @param EnvironmentInterface $environment The environment.
     *
     * @param PropertyInterface    $property    The property.
     *
     * @param ModelInterface       $model       The current model.
     *
     * @return \Widget
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function buildWidget(
        EnvironmentInterface $environment,
        PropertyInterface $property,
        ModelInterface $model
    ) {
        $dispatcher   = $environment->getEventDispatcher();
        $propertyName = $property->getName();
        $propExtra    = $property->getExtra();
        $defName      = $environment->getDataDefinition()->getName();
        $strClass     = $this->getWidgetClass($property);
        if (null === $strClass) {
            return null;
        }

        $event = new DecodePropertyValueForWidgetEvent($environment, $model);
        $event
            ->setProperty($propertyName)
            ->setValue($model->getProperty($propertyName));

        $dispatcher->dispatch($event::NAME, $event);
        $varValue = $event->getValue();

        $propExtra['required']  = ($varValue == '') && !empty($propExtra['mandatory']);
        $propExtra['tableless'] = true;
        if (isset($propExtra['datepicker'])) {
            $propExtra['class'] = $this->addCssClass($propExtra['class'], 'datepicker');
            $propExtra['class'] = $this->addCssClass($propExtra['class'], '-' . $propExtra['rgxp']);
        }
        if (isset($propExtra['colorpicker'])) {
            $propExtra['class'] = $this->addCssClass($propExtra['class'], 'colorpicker');
            if (isset($propExtra['isHexColor'])) {
                $propExtra['class'] = $this->addCssClass($propExtra['class'], '-hex-color');
            }
        }
        if (isset($propExtra['rte'])) {
            $propExtra['class'] = $this->addCssClass($propExtra['class'], 'rte');
            $propExtra['class'] = $this->addCssClass($propExtra['class'], '-' . $propExtra['rte']);
        }

        // Add the (backend) css class for the frontend as well.
        if (isset($propExtra['tl_class'])) {
            $this->addCssClass($propExtra['class'], $propExtra['tl_class']);
        }

        $arrConfig = array(
            'inputType' => $property->getWidgetType(),
            'label'     => array(
                $property->getLabel(),
                $property->getDescription()
            ),
            'options'   => $this->getOptionsForWidget($environment, $property, $model),
            'eval'      => $propExtra,
        );

        if (isset($propExtra['reference'])) {
            $arrConfig['reference'] = $propExtra['reference'];
        }

        $event = new GetAttributesFromDcaEvent(
            $arrConfig,
            $property->getName(),
            $varValue,
            $propertyName,
            $defName,
            new DcCompat($environment, $model, $propertyName)
        );

        $dispatcher->dispatch(ContaoEvents::WIDGET_GET_ATTRIBUTES_FROM_DCA, $event);
        $preparedConfig = $event->getResult();

        // Remove the "Backend.autoSubmit()", add css class for this purpose
        if (isset($preparedConfig['submitOnChange'])) {
            $preparedConfig['class'] = $this->addCssClass($preparedConfig['class'], 'submitOnChange');
            unset($preparedConfig['onclick'], $preparedConfig['onchange']);
        }

        $widget = new $strClass($preparedConfig, new DcCompat($environment, $model, $propertyName));

        $widget->currentRecord = $model->getId();

        $event = new ManipulateWidgetEvent($environment, $model, $property, $widget);
        $dispatcher->dispatch(ManipulateWidgetEvent::NAME, $event);

        return $widget;
    }

    /**
     * Try to resolve the class name for the widget.
     *
     * @param PropertyInterface $property The property to get the widget class name for.
     *
     * @return string
     *
     * @SuppressWarnings(PHPMD.Superglobals)
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
     */
    private function getWidgetClass(PropertyInterface $property)
    {
        if (!isset($GLOBALS['TL_FFL'][$property->getWidgetType()])) {
            return null;
        }

        $className = $GLOBALS['TL_FFL'][$property->getWidgetType()];
        if (!class_exists($className)) {
            return null;
        }

        return $className;
    }

    /**
     * Get special labels.
     *
     * @param EnvironmentInterface $environment The environment.
     *
     * @param PropertyInterface    $propInfo    The property for which the options shall be retrieved.
     *
     * @param ModelInterface       $model       The model.
     *
     * @return string[]
     */
    private function getOptionsForWidget(
        EnvironmentInterface $environment,
        PropertyInterface $propInfo,
        ModelInterface $model
    ) {
        $dispatcher = $environment->getEventDispatcher();
        $options    = $propInfo->getOptions();
        $event      = new GetPropertyOptionsEvent($environment, $model);
        $event->setPropertyName($propInfo->getName());
        $event->setOptions($options);
        $dispatcher->dispatch(GetPropertyOptionsEvent::NAME, $event);

        if ($event->getOptions() !== $options) {
            return $event->getOptions();
        }

        return $options;
    }

    /**
     * Add a css class to a string of existing css classes.
     *
     * @param string $classString The string to append the css class to.
     * @param string $class       The css class to add.
     *
     * @return string
     */
    private function addCssClass($classString, $class)
    {
        if (null !== $classString) {
            $classString .= ' ' . $class;
        } else {
            $classString = $class;
        }

        return $classString;
    }
}


<?php

/**
 * This file is part of contao-community-alliance/dc-general-contao-frontend.
 *
 * (c) 2015-2018 Contao Community Alliance.
 *
 * 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    contao-community-alliance/dc-general-contao-frontend
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Richard Henkenjohann <richardhenkenjohann@googlemail.com>
 * @copyright  2015-2018 Contao Community Alliance.
 * @license    https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0
 * @filesource
 */

namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Listener;

use Contao\CoreBundle\Exception\RedirectResponseException;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\HandleSubmitEvent;
use ContaoCommunityAlliance\DcGeneral\Data\ModelId;
use ContaoCommunityAlliance\UrlBuilder\UrlBuilder;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * This class handles the submit buttons in the frontend for "save" and "save and create".
 */
class HandleSubmitListener
{

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

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

    /**
     * HandleSubmitListener constructor.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The request scope determinator.
     * @param RequestStack             $requestStack      The request stack.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator, RequestStack $requestStack)
    {
        $this->scopeDeterminator = $scopeDeterminator;
        $this->requestStack      = $requestStack;
    }

    /**
     * Handle the event.
     *
     * @param HandleSubmitEvent $event The event.
     *
     * @return void
     *
     * @throws RedirectResponseException To redirect to the proper edit mask.
     */
    public function handleEvent(HandleSubmitEvent $event): void
    {
        // Only run in the frontend
        if (false === $this->scopeDeterminator->currentScopeIsFrontend()) {
            return;
        }

        $currentUrl = $this->requestStack->getCurrentRequest()->getUri();

        switch ($event->getButtonName()) {
            case 'save':
                // Could be a create action, always redirect to current page with edit action and id properly set.
                $url = UrlBuilder::fromUrl($currentUrl)
                    ->setQueryParameter('act', 'edit')
                    ->setQueryParameter('id', ModelId::fromModel($event->getModel())->getSerialized());

                throw new RedirectResponseException($url->getUrl());
            case 'saveNcreate':
                // We want to create a new model, set create action and pass the current id as "after" to keep sorting.
                $after = ModelId::fromModel($event->getModel());
                $url   = UrlBuilder::fromUrl($currentUrl)
                    ->unsetQueryParameter('id')
                    ->setQueryParameter('act', 'create')
                    ->setQueryParameter('after', $after->getSerialized());

                throw new RedirectResponseException($url->getUrl());
            default:
                // Do nothing.
        }
    }
}


<?php

/**
 * This file is part of contao-community-alliance/dc-general-contao-frontend.
 *
 * (c) 2015-2018 Contao Community Alliance.
 *
 * 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    contao-community-alliance/dc-general-contao-frontend
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Richard Henkenjohann <richardhenkenjohann@googlemail.com>
 * @copyright  2015-2018 Contao Community Alliance.
 * @license    https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0
 * @filesource
 */

namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View;

use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetEditModeButtonsEvent;
use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\DcGeneralFrontendEvents;
use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\HandleSubmitEvent;
use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\PropertiesDefinitionInterface;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\PaletteInterface;
use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface;
use ContaoCommunityAlliance\DcGeneral\Data\PropertyValueBag;
use ContaoCommunityAlliance\DcGeneral\DcGeneralEvents;
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
use ContaoCommunityAlliance\DcGeneral\Event\EnforceModelRelationshipEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PostPersistModelEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PreEditModelEvent;
use ContaoCommunityAlliance\DcGeneral\Event\PrePersistModelEvent;
use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralInvalidArgumentException;
use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException;
use ContaoCommunityAlliance\Translator\TranslatorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * This class manages the displaying of the edit/create mask containing the widgets.
 *
 * It also handles the persisting of the model.
 */
class EditMask
{
    /**
     * The environment.
     *
     * @var EnvironmentInterface
     */
    private $environment;

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

    /**
     * Retrieve the translation manager to use.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * The data definition from the environment.
     *
     * @var ContainerInterface
     */
    private $definition;

    /**
     * The data provider of the model being edited.
     *
     * @var DataProviderInterface
     */
    private $modelProvider;

    /**
     * The model to be manipulated.
     *
     * @var ModelInterface
     */
    private $model;

    /**
     * The original model from the database.
     *
     * @var ModelInterface
     */
    private $originalModel;

    /**
     * The method to be executed before the model is persisted.
     *
     * @var callable|null
     */
    private $preFunction;

    /**
     * The method to be executed after the model is persisted.
     *
     * @var callable|null
     */
    private $postFunction;

    /**
     * The errors from the widgets.
     *
     * @var array
     */
    private $errors = [];

    /**
     * Create the edit mask.
     *
     * @param EnvironmentInterface $environment   The view in use.
     *
     * @param ModelInterface       $model         The model with the current data.
     *
     * @param ModelInterface       $originalModel The data from the original data.
     *
     * @param callable             $preFunction   The function to call before saving an item.
     *
     * @param callable             $postFunction  The function to call after saving an item.
     */
    public function __construct($environment, $model, $originalModel, $preFunction, $postFunction)
    {
        $providerName        = $model->getProviderName();
        $this->environment   = $environment;
        $this->translator    = $environment->getTranslator();
        $this->dispatcher    = $environment->getEventDispatcher();
        $this->definition    = $environment->getDataDefinition();
        $this->modelProvider = $this->definition->getDataProviderDefinition()->getInformation($providerName);
        $this->model         = $model;
        $this->originalModel = $originalModel;
        $this->preFunction   = $preFunction;
        $this->postFunction  = $postFunction;
    }

    /**
     * Create the edit mask.
     *
     * @return string
     *
     * @throws DcGeneralRuntimeException         If the data container is not editable, closed.
     *
     * @throws DcGeneralInvalidArgumentException If an unknown property is encountered in the palette.
     */
    public function execute()
    {
        $inputProvider      = $this->environment->getInputProvider();
        $palettesDefinition = $this->definition->getPalettesDefinition();
        $isSubmitted        = ($inputProvider->getValue('FORM_SUBMIT') === $this->definition->getName());
        $isAutoSubmit       = ($inputProvider->getValue('SUBMIT_TYPE') === 'auto');
        $widgetManager      = new WidgetManager($this->environment, $this->model);

        $this->dispatcher->dispatch(PreEditModelEvent::NAME, new PreEditModelEvent($this->environment, $this->model));

        $this->enforceModelRelationship();

        // Pass 1: Get the palette for the values stored in the model.
        $palette = $palettesDefinition->findPalette($this->model);

        $propertyValues = $this->processInput($widgetManager);
        if ($isSubmitted && $propertyValues) {
            // Pass 2: Determine the real palette we want to work on if we have some data submitted.
            $palette = $palettesDefinition->findPalette($this->model, $propertyValues);

            // Update the model - the model might add some more errors to the propertyValueBag via exceptions.
            $this->environment->getController()->updateModelFromPropertyBag($this->model, $propertyValues);
        }

        $fieldSets = $this->buildFieldSet($widgetManager, $palette, $propertyValues);

        $buttons = $this->getEditButtons();

        if ((!$isAutoSubmit) && $isSubmitted && empty($this->errors)) {
            $this->doPersist();
            $this->handleSubmit($buttons);
        }

        $template = new ViewTemplate('dcfe_general_edit');
        $template->setData(
            [
                'fieldsets'   => $fieldSets,
                'subHeadline' => $this->getHeadline(),
                'table'       => $this->definition->getName(),
                'enctype'     => 'multipart/form-data',
                'error'       => $this->errors,
                'editButtons' => $buttons
            ]
        );

        return $template->parse();
    }

    /**
     * This method triggers the event to update the parent child relationships of the current model.
     *
     * @return void
     */
    private function enforceModelRelationship()
    {
        $event = new EnforceModelRelationshipEvent($this->environment, $this->model);

        $this->dispatcher->dispatch(DcGeneralEvents::ENFORCE_MODEL_RELATIONSHIP, $event);
    }

    /**
     * Process input and return all modified properties or null if there is no input.
     *
     * @param WidgetManager $widgetManager The widget manager in use.
     *
     * @return null|PropertyValueBag
     */
    private function processInput($widgetManager)
    {
        $input = $this->environment->getInputProvider();

        if ($input->getValue('FORM_SUBMIT') === $this->definition->getName()) {
            $propertyValues = new PropertyValueBag();
            $propertyNames  = array_intersect(
                $this->definition->getPropertiesDefinition()->getPropertyNames(),
                (array) $input->getValue('FORM_INPUTS')
            );

            // Process input and update changed properties.
            foreach ($propertyNames as $propertyName) {
                $propertyValue = $input->hasValue($propertyName) ? $input->getValue($propertyName, true) : null;
                $propertyValues->setPropertyValue($propertyName, $propertyValue);
            }

            $widgetManager->processInput($propertyValues);

            return $propertyValues;
        }

        return null;
    }

    /**
     * Trigger the pre persist event and handle the prePersist function if available.
     *
     * @return void
     */
    private function handlePrePersist()
    {
        if (null !== $this->preFunction) {
            \call_user_func($this->preFunction, $this->environment, $this->model, $this->originalModel);
        }

        $this->dispatcher->dispatch(
            PrePersistModelEvent::NAME,
            new PrePersistModelEvent($this->environment, $this->model, $this->originalModel)
        );
    }

    /**
     * Trigger the post persist event and handle the postPersist function if available.
     *
     * @return void
     */
    private function handlePostPersist()
    {
        if (null !== $this->postFunction) {
            \call_user_func($this->postFunction, $this->environment, $this->model, $this->originalModel);
        }

        $event = new PostPersistModelEvent($this->environment, $this->model, $this->originalModel);
        $this->dispatcher->dispatch($event::NAME, $event);
    }

    /**
     * Get a translated label from the translator.
     *
     * The fallback is as follows:
     * 1. Try to translate via the data definition name as translation section.
     * 2. Try to translate with the prefix 'MSC.'.
     * 3. Return the input value as nothing worked out.
     *
     * @param string $transString The non translated label for the button.
     *
     * @param array  $parameters  The parameters to pass to the translator.
     *
     * @return string
     */
    private function translateLabel($transString, $parameters = [])
    {
        $translator = $this->translator;
        if ($transString !== ($label =
                $translator->translate($transString, $this->definition->getName(), $parameters))) {
            return $label;
        }

        if ($transString !== ($label = $translator->translate('MSC.'.$transString, $parameters))
        ) {
            return $label;
        }

        // Fallback, just return the key as is it.
        return $transString;
    }

    /**
     * Retrieve a list of html buttons to use in the bottom panel (submit area).
     *
     * @return string[]
     */
    private function getEditButtons()
    {
        $buttons = [];

        $buttons['save'] = sprintf(
            '<input type="submit" name="save" class="submit save" accesskey="s" value="%s">',
            $this->translateLabel('save')
        );

        if ($this->definition->getBasicDefinition()->isCreatable()) {
            $buttons['saveNcreate'] = sprintf(
                '<input type="submit" name="saveNcreate" class="submit saveNcreate" accesskey="n" ' .
                ' value="%s">',
                $this->translateLabel('saveNcreate')
            );
        }

        $event = new GetEditModeButtonsEvent($this->environment);
        $event->setButtons($buttons);

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

        return $event->getButtons();
    }

    /**
     * Build the field sets.
     *
     * @param WidgetManager    $widgetManager  The widget manager in use.
     *
     * @param PaletteInterface $palette        The palette to use.
     *
     * @param PropertyValueBag $propertyValues The property values.
     *
     * @return array
     *
     * @throws DcGeneralRuntimeException         For unknown properties.
     * @throws DcGeneralInvalidArgumentException When the property is not registered in the definition.
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    private function buildFieldSet($widgetManager, $palette, $propertyValues)
    {
        $propertyDefinitions = $this->definition->getPropertiesDefinition();
        $isAutoSubmit        = ($this->environment->getInputProvider()->getValue('SUBMIT_TYPE') === 'auto');

        $fieldSets = [];
        $errors    = [];
        $first     = true;
        foreach ($palette->getLegends() as $legend) {
            $legendName = $this->translator->translate(
                $legend->getName() . '_legend',
                $this->definition->getName()
            );
            $fields     = [];
            $hidden     = [];
            $properties = $legend->getProperties($this->model, $propertyValues);

            if (!$properties) {
                continue;
            }

            foreach ($properties as $property) {
                $propertyName = $property->getName();
                $this->ensurePropertyExists($propertyName, $propertyDefinitions);

                // If this property is invalid, fetch the error.
                if ((!$isAutoSubmit)
                    && $propertyValues
                    && $propertyValues->hasPropertyValue($propertyName)
                    && $propertyValues->isPropertyValueInvalid($propertyName)
                ) {
                    $errors[] = $propertyValues->getPropertyValueErrors($propertyName);
                }

                $fields[] = $widgetManager->renderWidget($propertyName, $isAutoSubmit, $propertyValues);
                $hidden[] = sprintf('<input type="hidden" name="FORM_INPUTS[]" value="%s">', $propertyName);
            }

            $fieldSet['label']   = $legendName;
            $fieldSet['class']   = $first ? 'tl_tbox' : 'tl_box';
            $fieldSet['palette'] = implode('', $hidden) . implode('', $fields);
            $fieldSet['legend']  = $legend->getName();
            $fieldSets[]         = $fieldSet;

            $first = false;
        }

        if ([] !== $errors) {
            $this->errors = array_merge(...$errors);
        }

        return $fieldSets;
    }

    /**
     * Ensure a property is defined in the data definition and raise an exception if it is unknown.
     *
     * @param string                        $property            The property name to check.
     *
     * @param PropertiesDefinitionInterface $propertyDefinitions The property definitions.
     *
     * @return void
     *
     * @throws DcGeneralInvalidArgumentException When the property is not registered in the definition.
     */
    private function ensurePropertyExists($property, $propertyDefinitions)
    {
        if (!$propertyDefinitions->hasProperty($property)) {
            throw new DcGeneralInvalidArgumentException(
                sprintf(
                    'Property %s is mentioned in palette but not defined in propertyDefinition.',
                    $property
                )
            );
        }
    }

    /**
     * Update the versioning information in the data provider for a given model (if necessary).
     *
     * @param ModelInterface $model The model to update.
     *
     * @return void
     */
    private function storeVersion(ModelInterface $model)
    {
        if (!$this->modelProvider->isVersioningEnabled()) {
            return;
        }

        $environment    = $this->environment;
        $modelId        = $model->getId();
        $dataProvider   = $environment->getDataProvider($this->model->getProviderName());
        $currentVersion = $dataProvider->getActiveVersion($modelId);
        // Compare version and current record.
        if (!$currentVersion
            || !$dataProvider->sameModels($model, $dataProvider->getVersion($modelId, $currentVersion))
        ) {
            $user     = \FrontendUser::getInstance();
            $username = '(frontend anonymous)';
            if ($user->authenticate()) {
                $username = $user->username;
            }

            $dataProvider->saveVersion($model, $username);
        }
    }

    /**
     * Handle the submit and determine which button has been triggered.
     *
     * This method will redirect the client.
     *
     * @param array $buttons The registered edit buttons.
     *
     * @return void
     */
    private function handleSubmit($buttons)
    {
        $inputProvider = $this->environment->getInputProvider();
        foreach (array_keys($buttons) as $button) {
            if ($inputProvider->hasValue($button)) {
                $event = new HandleSubmitEvent($this->environment, $this->model, $button);

                $this->dispatcher->dispatch(DcGeneralFrontendEvents::HANDLE_SUBMIT, $event);

                break;
            }
        }
    }

    /**
     * Determine the headline to use.
     *
     * @return string.
     */
    private function getHeadline()
    {
        if ($this->model->getId()) {
            return $this->translateLabel('editRecord', [$this->model->getId()]);
        }
        return $this->translateLabel('newRecord');
    }

    /**
     * Handle the persisting of the currently loaded model.
     *
     * @return void
     */
    private function doPersist()
    {
        if (!$this->model->getMeta(ModelInterface::IS_CHANGED)) {
            return;
        }

        $this->handlePrePersist();

        // TO DO: manual sorting property handling is not enabled here as it originates from the backend defininiton.
        // Save the model.
        $dataProvider = $this->environment->getDataProvider($this->model->getProviderName());

        $dataProvider->save($this->model);

        $this->handlePostPersist();

        $this->storeVersion($this->model);
    }
}


<?php namespace Subfission\Cas\Middleware;

use Closure;
use Illuminate\Contracts\Auth\Guard;

class RedirectCASAuthenticated {

	protected $auth;
	protected $cas;

	public function __construct(Guard $auth)
	{
		$this->auth = $auth;
		$this->cas = app('cas');
	}

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
		if($this->cas->checkAuthentication())
		{
			return redirect(config('cas.cas_redirect_path'));
		}

		return $next($request);
	}
}


<?php namespace Subfission\Cas;

use phpCAS;

class CasManager {

	/**
	 * Array for storing configuration settings.
	 */
	protected $config;

	/**
	 * Attributes used for overriding or masquerading.
	 */
	protected $_attributes = [];

	/**
	 * Boolean flag used for masquerading as a user.
	 */
	protected $_masquerading = false;

	/**
	 * @param array $config
	 */
	public function __construct( array $config ) {
		$this->parseConfig( $config );
		if ( $this->config['cas_debug'] === true ) {
			phpCAS::setDebug();
			phpCAS::log( 'Loaded configuration:' . PHP_EOL
			             . serialize( $config ) );
		} else {
			phpCAS::setDebug( $this->config['cas_debug'] );
		}

		phpCAS::setVerbose( $this->config['cas_verbose_errors'] );

		// Fix for PHP 7.2.  See http://php.net/manual/en/function.session-name.php
		if ( !headers_sent() && session_id() == "" ) {
			session_name( $this->config['cas_session_name'] );

			// Harden session cookie to prevent some attacks on the cookie (e.g. XSS)
			session_set_cookie_params( $this->config['cas_session_lifetime'],
				$this->config['cas_session_path'],
				env( 'APP_DOMAIN' ),
				env( 'HTTPS_ONLY_COOKIES' ),
				$this->config['cas_session_httponly'] );
		}

		$this->configureCas( $this->config['cas_proxy'] ? 'proxy' : 'client' );

		$this->configureCasValidation();

		// set login and logout URLs of the CAS server
		phpCAS::setServerLoginURL( $this->config['cas_login_url'] );

		// If specified, this will override the URL the user will be returning to.
		if ( $this->config['cas_redirect_path'] ) {
			phpCAS::setFixedServiceURL( $this->config['cas_redirect_path'] );
		}

		phpCAS::setServerLogoutURL( $this->config['cas_logout_url'] );

		if ( $this->config['cas_masquerade'] ) {
			$this->_masquerading = true;
			phpCAS::log( 'Masquerading as user: '
			             . $this->config['cas_masquerade'] );
		}
	}

	/**
	 * Configure CAS Client|Proxy
	 *
	 * @param $method
	 */
	protected function configureCas( $method = 'client' ) {
		if ( $this->config['cas_enable_saml'] ) {
			$server_type = SAML_VERSION_1_1;
		} else {
			// This allows the user to use 1.0, 2.0, etc as a string in the config
			$cas_version_str = 'CAS_VERSION_' . str_replace( '.', '_',
					$this->config['cas_version'] );

			// We pull the phpCAS constant values as this is their definition
			// PHP will generate a E_WARNING if the version string is invalid which is helpful for troubleshooting
			$server_type = constant( $cas_version_str );

			if ( is_null( $server_type ) ) {
				// This will never be null, but can be invalid values for which we need to detect and substitute.
				phpCAS::log( 'Invalid CAS version set; Reverting to defaults' );
				$server_type = CAS_VERSION_2_0;
			}
		}

		phpCAS::$method( $server_type, $this->config['cas_hostname'],
			(int) $this->config['cas_port'],
			$this->config['cas_uri'], $this->config['cas_control_session'] );

		if ( $this->config['cas_enable_saml'] ) {
			// Handle SAML logout requests that emanate from the CAS host exclusively.
			// Failure to restrict SAML logout requests to authorized hosts could
			// allow denial of service attacks where at the least the server is
			// tied up parsing bogus XML messages.
			phpCAS::handleLogoutRequests( true,
				explode( ',', $this->config['cas_real_hosts'] ) );
		}
	}

	/**
	 * Maintain backwards compatibility with config file
	 *
	 * @param array $config
	 */
	protected function parseConfig( array $config ) {

		$defaults = [
			'cas_hostname'         => '',
			'cas_session_name'     => 'CASAuth',
			'cas_session_lifetime' => 7200,
			'cas_session_path'     => '/',
			'cas_control_session'  => false,
			'cas_session_httponly' => true,
			'cas_port'             => 443,
			'cas_uri'              => '/cas',
			'cas_validation'       => '',
			'cas_cert'             => '',
			'cas_proxy'            => false,
			'cas_validate_cn'      => true,
			'cas_login_url'        => '',
			'cas_logout_url'       => 'https://cas.myuniv.edu/cas/logout',
			'cas_logout_redirect'  => '',
			'cas_redirect_path'    => '',
			'cas_enable_saml'      => true,
			'cas_version'          => "2.0",
			'cas_debug'            => false,
			'cas_verbose_errors'   => false,
			'cas_masquerade'       => ''
		];

		$this->config = array_merge( $defaults, $config );
	}

	/**
	 * Configure SSL Validation
	 *
	 * Having some kind of server cert validation in production
	 * is highly recommended.
	 */
	protected function configureCasValidation() {
		if ( $this->config['cas_validation'] == 'ca'
		     || $this->config['cas_validation'] == 'self' ) {
			phpCAS::setCasServerCACert( $this->config['cas_cert'],
				$this->config['cas_validate_cn'] );
		} else {
			// Not safe (does not validate your CAS server)
			phpCAS::setNoCasServerValidation();
		}
	}

	/**
	 * Authenticates the user based on the current request.
	 *
	 * @return bool
	 */
	public function authenticate() {
		if ( $this->isMasquerading() ) {
			return true;
		}

		return phpCAS::forceAuthentication();
	}

	/**
	 * Returns the current config.
	 *
	 * @return array
	 */
	public function getConfig() {
		return $this->config;
	}

	/**
	 * Retrieve authenticated credentials.
	 * Returns either the masqueraded account or the phpCAS user.
	 *
	 * @return string
	 */
	public function user() {
		if ( $this->isMasquerading() ) {
			return $this->config['cas_masquerade'];
		}

		return phpCAS::getUser();
	}

	public function getCurrentUser() {
		return $this->user();
	}

	/**
	 * Retrieve a specific attribute by key name.  The
	 * attribute returned can be either a string or
	 * an array based on matches.
	 *
	 * @param $key
	 *
	 * @return mixed
	 */
	public function getAttribute( $key ) {
		if ( ! $this->isMasquerading() ) {
			return phpCAS::getAttribute( $key );
		}
		if ( $this->hasAttribute( $key ) ) {
			return $this->_attributes[ $key ];
		}

		return;
	}

	/**
	 * Check for the existence of a key in attributes.
	 *
	 * @param $key
	 *
	 * @return boolean
	 */
	public function hasAttribute( $key ) {
		if ( $this->isMasquerading() ) {
			return array_key_exists( $key, $this->_attributes );
		}

		return phpCAS::hasAttribute( $key );
	}

	/**
	 * Logout of the CAS session and redirect users.
	 *
	 * @param string $url
	 * @param string $service
	 */
	public function logout( $url = '', $service = '' ) {
		if ( phpCAS::isSessionAuthenticated() ) {
			if ( isset( $_SESSION['phpCAS'] ) ) {
				$serialized = serialize( $_SESSION['phpCAS'] );
				phpCAS::log( 'Logout requested, but no session data found for user:'
				             . PHP_EOL . $serialized );
			}
		}
		$params = [];
		if ( $service ) {
			$params['service'] = $service;
		} elseif ( $this->config['cas_logout_redirect'] ) {
			$params['service'] = $this->config['cas_logout_redirect'];
		}
		if ( $url ) {
			$params['url'] = $url;
		}
		phpCAS::logout( $params );
		exit;
	}

	/**
	 * Logout the user using the provided URL.
	 *
	 * @param $url
	 */
	public function logoutWithUrl( $url ) {
		$this->logout( $url );
	}

	/**
	 * Get the attributes for for the currently connected user. This method
	 * can only be called after authenticate() or an error wil be thrown.
	 *
	 * @return mixed
	 */
	public function getAttributes() {
		// We don't error check because phpCAS has its own error handling.
		return $this->isMasquerading() ? $this->_attributes
			: phpCAS::getAttributes();
	}

	/**
	 * Checks to see is user is authenticated locally
	 *
	 * @return boolean
	 */
	public function isAuthenticated() {
		return $this->isMasquerading() ? true : phpCAS::isAuthenticated();
	}

	/**
	 * Checks to see is user is globally in CAS
	 *
	 * @return boolean
	 */
	public function checkAuthentication() {
		return $this->isMasquerading() ? true : phpCAS::checkAuthentication();
	}

	/**
	 * Checks to see if masquerading is enabled
	 *
	 * @return bool
	 */
	public function isMasquerading() {
		return $this->_masquerading;
	}

	/**
	 * Set the attributes for a user when masquerading. This
	 * method has no effect when not masquerading.
	 *
	 * @param array $attr : the attributes of the user.
	 */
	public function setAttributes( array $attr ) {
		$this->_attributes = $attr;
		phpCAS::log( 'Forced setting of user masquerading attributes: '
		             . serialize( $attr ) );
	}

	/**
	 * Pass through undefined methods to phpCAS
	 *
	 * @param $method
	 * @param $params
	 *
	 * @return mixed
	 */
	public function __call( $method, $params ) {
		if ( method_exists( 'phpCAS', $method )
		     && is_callable( [
				'phpCAS',
				$method
			] ) ) {
			return call_user_func_array( [ 'phpCAS', $method ], $params );
		}
		throw new \BadMethodCallException( 'Method not callable in phpCAS '
		                                   . $method . '::' . print_r( $params,
				true ) );
	}
}


<?php namespace Subfission\Cas\Middleware;

use Closure;
use Illuminate\Contracts\Auth\Guard;

class CASAuth
{

    protected $auth;
    protected $cas;

    public function __construct(Guard $auth)
    {
        $this->auth = $auth;
        $this->cas = app('cas');
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if( $this->cas->checkAuthentication() )
        {
            // Store the user credentials in a Laravel managed session
            session()->put('cas_user', $this->cas->user());
        } else {
            if ($request->ajax() || $request->wantsJson()) {
                return response('Unauthorized.', 401);
            }
            $this->cas->authenticate();
        }

        return $next($request);
    }
}


<?php

namespace ZDB ;

/**
 * Синглетон  инкапсулирующий   конект  к  БД
 */
class DB
{

    private static $db = null, $driver = null;
    private static $dbhost, $dbname, $dbuser, $dbpassword;
    private $conn = null;

    private function __construct()
    {

    }

    /**
     * Установка  параметров   коннекта
     *
     * @param mixed $dbhost
     * @param mixed $dbname
     * @param mixed $dbuser
     * @param mixed $dbpassword
     * @param string $driver
     */
    public static function config($dbhost, $dbname, $dbuser, $dbpassword, $driver = "mysqli")
    {
        global $ADODB_FETCH_MODE, $ADODB_QUOTE_FIELDNAMES;
        $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
        $ADODB_QUOTE_FIELDNAMES = true;

        self::$dbhost = $dbhost;
        self::$dbname = $dbname;
        self::$dbuser = $dbuser;
        self::$dbpassword = $dbpassword;
        self::$driver = $driver;
    }

    /**
     * Открывает  конект  к  БД  и  возвращает  соотаветствующий  ресурс
     *
     */
    public static function getConnect()
    {
        $db = DB::getDB();
        $db->open();

        return $db->conn;
    }

    /**
     * Возвращает  инстанс
     *
     */
    public static function getDB()
    {
        if (self::$db == null) {
            self::$db = new DB();
        }
        return self::$db;
    }

    private function open()
    {
        if ($this->conn instanceof \ADOConnection) {
            return;
        }
        $this->conn = \ADONewConnection(self::$driver);
        $this->conn->Connect(self::$dbhost, self::$dbuser, self::$dbpassword, self::$dbname);
        if(DB::$driver == "mysqli"){
            $this->conn->Execute("SET NAMES 'utf8'");
        }

    }

    /**
     * Закрывает  конект  к  БД
     *
     */
    public static function Close()
    {
        $db = DB::getDB();
        if ($db->conn instanceof \ADOConnection) {
            $db->conn->Close();
        }
    }

}


<?php

namespace ZDB ;

/**
 * Базовый  класс  для  бизнес-сущностей
 * Реализует паттерн  Active Directory
 * Предназначен  для   автоматизации стандартных  над  записями  в  БД
 */
abstract class Entity
{

    protected $fields = array();  //список  полей

    /**
     * Конструктор
     *
     * @param mixed $row массив инициализирующий некторые
     * или  все  поля объекта
     *
     */

    function __construct($row = null)
    {
        $this->init();
        if (is_array($row)) {
            $this->fields = array_merge($this->fields, $row);
        }
    }

    /**
     * Инициализация полей  сущности
     *
     */
    protected function init()
    {
        $meta = $this->getMetadata();
        $this->{$meta['keyfield']} = 0;
    }

    /**
     * Возврашает  метаданные для   выборки  из  БД
     * Реализуется  конкретными  сущностями имплементирующими  класс  Entity
     * Метаданные  содержат  имя  таблицы, имя  ключевого  поля
     * а  также  имя  представления  если  такое  существует  в  БД
     * Например  array('table' => 'system_users','view' => 'system_users_view', 'keyfield' => 'user_id')
     *
     * Вместо  испоользования  метода   можно  импользоввать  аннтации  возде  определения  класса
     * анноации  именуются   аналогично  ключам  массива метаданных.
     */
    protected static function getMetadata()
    {
        $class = new \ReflectionClass(get_called_class());
        $doc = $class->getDocComment();
        preg_match_all('/@([a-z0-9_-]+)=([^\n]+)/is', $doc, $arr);
        if (is_array($arr)) {
            $reg_arr = array_combine($arr[1], $arr[2]);

            $table = trim($reg_arr['table']);
            $view = trim(@$reg_arr['view']);
            $keyfield = trim($reg_arr['keyfield']);

            if (strlen($table) > 0 && strlen($keyfield) > 0) {
                $retarr = array();
                $retarr['table'] = $table;
                $retarr['keyfield'] = $keyfield;
                if (strlen($view) > 0)
                    $retarr['view'] = $view;

                return $retarr;
            }
        }

        throw new ZDBException('getMetadata должен  быть  перегружен');
    }

    /**
     * Возвращает  сущность  из  БД по  ключу
     * @param mixed $param
     */
    public static function load($param)
    {
        $row  = array();
        $class = get_called_class();

        $meta = $class::getMetadata();
        $table = isset($meta['view']) ? $meta['view'] : $meta['table'];
        $conn = DB::getConnect();
        if (is_numeric($param)) {
            $row = $conn->GetRow("select * from {$table}  where {$meta['keyfield']} = " . $param);
        } else
            if (is_array($param)) {
                $row = $param;
            }

        if (count($row) == 0) {
            return null;
        }
        $obj = new $class();
        $obj->fields = array_merge($obj->fields, $row);
        $obj->afterLoad();
        return $obj;
    }

    /**
     * Возвращает  количество  сущностй  в  БД  по  критерию
     *
     * @param mixed $where
     */
    public static function findCnt($where = "")
    {
        $class = get_called_class();
        $meta = $class::getMetadata();
        $conn = DB::getConnect();

        $table = isset($meta['view']) ? $meta['view'] : $meta['table'];
        $sql = "select coalesce(count({$meta['keyfield']}),0) as  cnt from " . $table;

        if (strlen($where) > 0) {
            $sql .= " where " . $where;
        }

        return $conn->getOne($sql);
    }

    public static function findBySql($sql)
    {

        $class = get_called_class();
        $meta = $class::getMetadata();
        //$table = isset($meta['view']) ? $meta['view'] : $meta['table'];
        $conn = DB::getConnect();
        $list = array();

        $rs = $conn->Execute($sql);

        foreach ($rs as $row) {
            $list[$row[$meta['keyfield']]] = new $class($row);
            $list[$row[$meta['keyfield']]]->afterLoad();
        }
        return $list;
    }

    /**
     * Возвращает  массив ключ/имя  из  БД  по  критерию
     * Может  использоватся  для заполнения выпадающих списков
     *
     * @param string $fieldname Имя  поля представляющее имя сущности.
     * @param mixed $where Условие  для предиката where
     * @param mixed $orderbyfield
     * @param mixed $orderbydir
     * @param mixed $count
     * @param mixed $offset
     */
    public static function findArray($fieldname, $where = '', $orderbyfield = null, $count = -1, $offset = -1)
    {
        $entitylist = self::find($where, $orderbyfield, $count, $offset);

        $list = array();
        foreach ($entitylist as $key => $value) {
            $list[$key] = $value->{$fieldname};
        }

        return $list;
    }

    /**
     * Возвращает  массив  сущностей  из  БД  по  критерию
     *
     * @param mixed $where Условие  для предиката where
     * @param mixed $orderbyfield
     * @param mixed $orderbydir
     * @param mixed $count
     * @param mixed $offset
     */
    public static function find($where = '', $orderbyfield = null, $count = -1, $offset = -1)
    {

        $class = get_called_class();
        $meta = $class::getMetadata();
        $table = isset($meta['view']) ? $meta['view'] : $meta['table'];
        $conn = DB::getConnect();
        $list = array();
        $sql = "select * from " . $table;

        if (strlen(trim($where)) > 0) {
            $sql .= " where " . $where;
        }

        if (strlen(trim($orderbyfield)) > 0) {
            $sql .= " order by " . $orderbyfield;

        }

        if ($offset >= 0 or $count >= 0) {
            $rs = $conn->SelectLimit($sql, $count, $offset);
        } else {
            $rs = $conn->Execute($sql);
        }

        foreach ($rs as $row) {
            $list[$row[$meta['keyfield']]] = new $class($row);
            $list[$row[$meta['keyfield']]]->afterLoad();
        }
        return $list;
    }

    /**
     * Возвращает  одну  строку  из набора
     * строки  должны  быть  уникальны
     * @param mixed $where
     * @deprecated  использовать   getFirst с параметром  unique=true
     */
    public static function findOne($where = "")
    {
        $list = self::find($where);

        if (count($list) == 0) {
            return null;
        }
        if (count($list) == 1) {

            return array_pop($list);
        }
        if (count($list) > 1) {
            throw new ZDBException("Метод findOne вернул  больше  одной  записи. Условие: [{$where}]");
        }
        return null;
    }

    /**
     * Возвращает  одно скалярное  значение  из одной строки
     * @param mixed $field  возвращаемое  поле  или  выражение
     * @param mixed $where
     */
    public static function getOne($field,$where = "")
    {
        $class = get_called_class();
        $meta = $class::getMetadata();
        $conn = DB::getConnect();

        $table = isset($meta['view']) ? $meta['view'] : $meta['table'];
        $sql = "select {$field} from " . $table;

        if (strlen($where) > 0) {
            $sql .= " where " . $where;
        }

        return $conn->getOne($sql);
    }
    /**
     * Возвращает  первую  строку  из набора
     * @param mixed $where
     * @param mixed $orderbyfield
     * @param mixed $unique  если  true должна быть  только одна запись
     */
    public static function getFirst($where = "", $orderbyfield = null,$unique=false)
    {
        $list = self::find($where, $orderbyfield, 1);

        if (count($list) == 0) {
            return null;
        }
        if ($unique ==true &&  count($list) > 1) {
            throw new ZDBException("Метод getFirst нащел  больше  одной  записи. Условие: [{$where}]");
        }
        return array_pop($list);

    }

    /**
     * Удаление  сущности
     * возвращает  false  если   удаление неудачно  или  не  разрешено
     * @param mixed $id Уникальный ключ
     */
    public static function delete($id)
    {
        $class = get_called_class();
        $meta = $class::getMetadata();

        if ($id>0) {

            $obj = $class::load($id);
        }

        if ($obj instanceof Entity) {
            $alowdelete = $obj->beforeDelete();
            if ($alowdelete === false) {
               // throw new ZDBException("Объект '{$meta['table']}' ({$id}) не может быть удален");
                 return false;
            }

            $sql = "delete from {$meta['table']}  where {$meta['keyfield']} = " . $id;
            $conn = DB::getConnect();
            $conn->Execute($sql);
            return true;
        }

    }

    /**
     * Вызывается перед  удалением  сущности
     * если  возвращает  false  удаление  отеняется
     *
     */
    protected function beforeDelete()
    {
        return true;
    }

    /**
     * Обработка строки  перед вставкой   в  запрос
     * после  обработки  строка  не  требует кавычек
     * @param mixed $str
     */
    public static function qstr($str)
    {
        $conn = DB::getConnect();
        return $conn->qstr($str);
    }

    /**
     * Добавление  слешей  в строку
     *
     * @param mixed $str
     */
    public static function escape($str)
    {
        $conn = DB::getConnect();
        return mysqli_real_escape_string($conn->_connectionID, $str);
    }

    /**
     * Форматирование  даты в   сответствии  с  SQL  диалектом
     *
     * @param mixed $dt Timestamp
     */
    public static function dbdate($dt)
    {
        $conn = DB::getConnect();
        return $conn->DBDate($dt);
    }

    /**
     * Возвращает  значение  поля
     *
     * @param mixed $name
     * @return mixed
     */
    public final function __get($name)
    {
        return $this->fields[$name];
    }

    /**
     * Устанавливает  значение поля
     *
     * @param mixed $name
     * @param mixed $value
     */
    public final function __set($name, $value)
    {
        $this->fields[$name] = $value;
    }

    /**
     * Возвращает поля сущности  в  виде  ассоциативного  массива
     *
     */
    public final function getData()
    {
        return $this->fields;
    }

    /**
     * Возвращает значение  уникального  ключа  сущности
     *
     */
    public final function getKeyValue()
    {
        $meta = $this->getMetadata();
        return $this->fields[$meta['keyfield']];
    }

    /**
     * Сохраняет  сущность  в  БД
     * Если  сущность новая создает запись
     *
     */
    public function save()
    {

        if ($this->beforeSave() === false) {
            return;
        };
        $conn = DB::getConnect();
        $meta = $this->getMetadata();
        $flist=$this->fields ;
        unset($flist[$meta['keyfield']] );//убираем  ключевое  поле с  запроса
        if ($this->fields[$meta['keyfield']] > 0) {

            $conn->AutoExecute($meta['table'], $flist, "UPDATE", "{$meta['keyfield']} = " . $this->fields[$meta['keyfield']]);
            $this->afterSave(true);
        } else {
            $conn->AutoExecute($meta['table'], $flist, "INSERT");
            $this->fields[$meta['keyfield']] = $conn->Insert_ID();
            $this->afterSave(false);
        }
    }

    /**
     * Вызывается  перед сохранением  сущности
     * Если  возвращает  false  сохранение  отменяется
     *
     */
    protected function beforeSave()
    {
        return true;
    }

    /**
     * Вызывается  после  сохранения сущности
     *
     * @param mixed $update - true  если обновление
     */
    protected function afterSave($update)
    {

    }

    /**
     * Вызывается   после  загрузки  сущности  из  БД
     *
     */
    protected function afterLoad()
    {

    }
}

class ZDBException extends \Exception
{
}


<?php
/**
 * Class representing a Value Object of the HTTP Status-Line, as
 * specified in RFC 2616 and RFC 7231.
 *
 * PHP version 5.3
 *
 * @category StatusLine
 *
 * @package Teapot\StatusLine
 *
 * @author    Barney Hanlon <barney@shrikeh.net>
 * @copyright 2013-2016 B Hanlon. All rights reserved.
 * @license   MIT http://opensource.org/licenses/MIT
 * @link      https://shrikeh.github.com/teapot
 */
namespace Teapot\StatusLine;

use Teapot\StatusCode;
use Teapot\StatusLine;
use Teapot\StatusCodeException\InvalidStatusCodeException;
use Psr\Http\Message\ResponseInterface;

/**
 * Class representing a Value Object of the HTTP Status-Line, as
 * specified in RFC 2616 and RFC 7231.
 *
 * PHP version 5.3
 *
 * @category StatusLine
 *
 * @package Teapot
 *
 * @author    Barney Hanlon <barney@shrikeh.net>
 * @copyright 2013-2016 B Hanlon. All rights reserved.
 * @license   MIT http://opensource.org/licenses/MIT
 * @link      https://shrikeh.github.com/teapot
 */
final class ResponseStatusLine implements StatusLine
{
    /**
     * The actual response code.
     *
     * @var int
     */
    private $code;

    /**
     * The reason phrase.
     *
     * @var string
     */
    private $reason;

    /**
     * ResponseStatusLine constructor.
     * @param int    $code   The HTTP response code
     * @param string $reason The reason phrase
     */
    public function __construct($code, $reason)
    {
        $this->setCode($code);
        $this->reason = $reason;
    }

    /**
     * Return the response code.
     *
     * @return int
     */
    public function statusCode()
    {
        return (int) $this->code;
    }

    /**
     * Return the reason phrase
     *
     * @return string
     */
    public function reasonPhrase()
    {
        return (string) $this->reason;
    }

    /**
     * Add the status code and reason phrase to a Response.
     *
     * @param ResponseInterface $response The response
     * @return ResponseInterface
     */
    public function response(ResponseInterface $response)
    {
        return $response->withStatus(
            $this->statusCode(),
            $this->reasonPhrase()
        );
    }

    /**
     * Return the status class of the response code.
     *
     * @return int
     */
    public function statusClass()
    {
        return (int) \floor($this->code / 100);
    }

    /**
     * Helper to establish if the class of the status code
     * is informational (1xx).
     *
     * @return bool
     */
    public function isInformational()
    {
        return $this->isStatusClass(StatusCode::INFORMATIONAL);
    }

    /**
     * Helper to establish if the class of the status code
     * is successful (2xx).
     *
     * @return bool
     */
    public function isSuccessful()
    {
        return $this->isStatusClass(StatusCode::SUCCESSFUL);
    }

    /**
     * Helper to establish if the class of the status code
     * is redirection (3xx).
     *
     * @return bool
     */
    public function isRedirection()
    {
        return $this->isStatusClass(StatusCode::REDIRECTION);
    }

    /**
     * Helper to establish if the class of the status code
     * is client error (4xx).
     *
     * @return bool
     */
    public function isClientError()
    {
        return $this->isStatusClass(StatusCode::CLIENT_ERROR);
    }

    /**
     * Helper to establish if the class of the status code
     * is server error (5xx).
     *
     * @return bool
     */
    public function isServerError()
    {
        return $this->isStatusClass(StatusCode::SERVER_ERROR);
    }

    /**
     * Set the code. Used in constructor to ensure the code meets the
     * requirements for a status code.
     *
     * @param int $code The status code
     * @throws InvalidStatusCodeException If the status code is invalid
     */
    private function setCode($code)
    {
        if (!\is_numeric($code)) {
            throw InvalidStatusCodeException::notNumeric($code);
        }
        $code = (int) $code;

        if ($code < 100) {
            throw InvalidStatusCodeException::notGreaterOrEqualTo100($code);
        }
        $this->code = $code;
    }

    /**
     * Test whether the response class matches the class passed to it.
     *
     * @param int $class The class of the response code
     * @return bool
     */
    private function isStatusClass($class)
    {
        return $this->statusClass() === $class;
    }
}


<?php
/**
 * Simple Exception to represent http-based Exceptions.
 *
 * PHP version 5.3
 *
 * @category Exception
 *
 * @package Teapot
 *
 * @author    Barney Hanlon <barney@shrikeh.net>
 * @copyright 2013-2016 B Hanlon. All rights reserved.
 * @license   MIT http://opensource.org/licenses/MIT
 *
 * @link https://shrikeh.github.com/teapot
 */
namespace Teapot;

/**
 * Simple Exception to represent http-based Exceptions.
 *
 * @category Exception
 *
 * @package Teapot
 *
 * @author    Barney Hanlon <barney@shrikeh.net>
 * @copyright 2013-2016 B Hanlon. All rights reserved.
 * @license   MIT http://opensource.org/licenses/MIT
 *
 * @link https://shrikeh.github.com/teapot
 */
class HttpException extends \Exception implements StatusCode
{
    /**
     * The standard HTTP 1.1 prefix.
     *
     * @var string
     */
    const HTTP1_1_PREFIX = 'HTTP/1.1';
    /**
     * Simple magic so you can use the Exception directly as a string, for
     * example in header();.
     *
     * @return string A fully valid status header
     */
    public function __toString()
    {
        // I dislike functionality inside magic methods,
        // so this just proxies to render().
        return $this->render();
    }

    /**
     * Render the code and message (in whole or in part) as a valid
     * response status header.
     *
     * @param bool $prependHttp Whether to prepend the HTTP/1.1 prefix
     *
     * @return string
     */
    public function render($prependHttp = true)
    {
        $string = $this->getCode().' '.$this->getMessage();
        if (true === $prependHttp) {
            $string = self::HTTP1_1_PREFIX.' '.$string;
        }

        return $string;
    }
}


<?php

namespace Saritasa\DingoApi\Paging;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

/**
 * Cursor result, that tries to automatically detect next, previous cursor values
 */
class CursorResultAuto extends CursorResult
{
    // Row number column name for cursor pagination by query with custom sorting.
    const ROW_NUM_COLUMN = 'query_row_number';

    /**
     * Cursor result, that tries to automatically detect next, previous cursor values
     *
     * @param CursorRequest $cursorRequest Original cursor request
     * @param Collection $items current page items to return
     * @param mixed $prev Cursor value for previous page
     *        (try to detect automatically, based on current cursor value and page size if null)
     * @param mixed $next Cursor value for next page (try to detect automatically as last item ID, if null)
     */
    public function __construct(CursorRequest $cursorRequest, Collection $items, $prev = null, $next = null)
    {
        $current = $cursorRequest->current ?: $this->getKeyOrNull($items->first());
        $count = $items->count();

        if ($next == null && $count >= $cursorRequest->pageSize) {
            $next = $this->getKeyOrNull($items->last());
        }

        if ($this->isIntegerKey($current, $items->first())) {
            $current = (int)$cursorRequest->current;
            if ($next != null) {
                $next = (int)$next;
            }
            $prev = $current ? $current - $cursorRequest->pageSize : null;
            if ($prev < 0) {
                $prev = null;
            }
        }

        parent::__construct($cursorRequest, $items, $prev, $next, $count);
    }

    /**
     * Try to get model row number or key. Return row number column if exists.
     */
    protected function getKeyOrNull($model)
    {
        if ($model == null) {
            return null;
        }
        return $model->{self::ROW_NUM_COLUMN} ?: ($model instanceof Model ? $model->getKey() : null);
    }

    /**
     * Detect, if cursor value is integer or not.
     * Extract key type from model, if possible, otherwise just check cursor value type
     *
     * @param mixed $current Current curor value
     * @param mixed $model one of items or null
     * @return boolean
     */
    protected function isIntegerKey($current, $model): bool
    {
        if ($model != null && $model instanceof Model) {
            return !empty($model->{self::ROW_NUM_COLUMN}) || $model->getKeyType() == 'int';
        } else {
            if ($current != null && is_numeric($current) || ctype_digit($current)) {
                return true;
            }
        }
        return false;
    }
}


<?php

namespace Saritasa\DingoApi;

use Dingo\Api\Provider\LaravelServiceProvider as DingoApiServiceProvider;
use Saritasa\DingoApi\Exceptions\ApiExceptionHandler;
use Illuminate\Support\ServiceProvider;

class SaritasaDingoApiServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->register(DingoApiServiceProvider::class);
        $this->registerApiExceptionHandler();

        $this->registerRouterHelper();

        $configPath = __DIR__ . '/../config/api.php';
        $this->mergeConfigFrom($configPath, 'api');
    }

    private function registerApiExceptionHandler()
    {
        $this->app->singleton('app.api.exception', ApiExceptionHandler::class);
        app('app.api.exception');
    }

    private function registerRouterHelper()
    {
        require_once __DIR__ . '/Helpers/router.php';
    }
}


<?php

/*
 * This file is part of con4gis,
 * the gis-kit for Contao CMS.
 *
 * @package    con4gis
 * @version    6
 * @author     con4gis contributors (see "authors.txt")
 * @license    LGPL-3.0-or-later
 * @copyright  Küstenschmiede GmbH Software & Design
 * @link       https://www.con4gis.org
 */

    // index.php is a frontend script
    define('TL_MODE', 'FE');
    // Start the session so we can access known request tokens
    @session_start();

    // Allow to bypass the token check
    if (!isset($_POST['REQUEST_TOKEN'])) {
        /**
         *
         */
        define('BYPASS_TOKEN_CHECK', true);
    }

    $initialize = $_SERVER["DOCUMENT_ROOT"].'/system/initialize.php';
    if (!file_exists($initialize)) {
        $initialize = '../../../../system/initialize.php';
    }

    // Initialize the system
    require_once($initialize);

    /**
     * Class Api4Gis
     */
    class Api4Gis extends Frontend
    {

        /**
         * @var string
         */
        private $_sApiUrl = 'src/con4gis/CoreBundle/Resources/contao/api/index.php';

        /**
         * Initialize the object
         */
        public function __construct()
        {

            // Load user object before calling the parent constructor
            $this->import('FrontendUser', 'User');
            parent::__construct();

            // Check whether a user is logged in
            define('BE_USER_LOGGED_IN', $this->getLoginStatus('BE_USER_AUTH'));
            define('FE_USER_LOGGED_IN', $this->getLoginStatus('FE_USER_AUTH'));
        }

        /**
         * Run the controller
         */
        public function run()
        {

            // Set default headers for api
            header('Content-Type: application/json');

            // Maintenance mode
            if ($GLOBALS['TL_CONFIG']['maintenanceMode'] && !BE_USER_LOGGED_IN) {
                header('HTTP/1.1 503 Service Unavailable');
                exit;
            }

            // Get path
            $arrFragments = $this->getFragmentsFromUrl();

            // Stop on empty path
            if (empty($arrFragments)) {
                header('HTTP/1.1 400 Bad Request');
                exit;
            }

            // Extract api endpoint
            $strApiEndpoint = array_shift($arrFragments);

            // check if its a test-call
            if ($strApiEndpoint == 'c4g_apicheck_ajax') {
                if (!$arrFragments[0] || array_key_exists($arrFragments[0], $GLOBALS['TL_API'])) {
                    return true;
                } else {
                    header('HTTP/1.1 501 Not Implemented');
                    exit;
                }
            }

            // Stop if no matching endpoint is found
            if (!array_key_exists($strApiEndpoint, $GLOBALS['TL_API'])) {
                header('HTTP/1.1 400 Bad Request');
                exit;
            }

            $blnUseCache = false;
            $blnOutputFromCache = false;

            if (!\Config::get('debugMode') && (\Config::get('cacheMode') == 'both' || \Config::get('cacheMode') == 'server') && in_array($strApiEndpoint, $GLOBALS['CON4GIS']['USE_CACHE']['SERVICES']))
            {
                $blnUseCache = true;
            }

            if (is_array($GLOBALS['CON4GIS']['USE_CACHE']['PARAMS']))
            {
                foreach ($GLOBALS['CON4GIS']['USE_CACHE']['PARAMS'] as $key=>$arrValues)
                {
                    if (\Input::get($key) && in_array(\Input::get($key), $arrValues))
                    {
                        $blnUseCache = true;
                    }
                }
            }

            if ($blnUseCache)
            {
                // check for cached data
                if ($strResponse = \con4gis\CoreBundle\Resources\contao\classes\C4GApiCache::getCacheData($strApiEndpoint, $arrFragments))
                {
                    $blnOutputFromCache = true;
                }
            }

            // Generate the result

            // check for jsonp request
            if (\Input::get('callback'))
            {
                echo \Input::get('callback') . '(';
            }

            if ($blnOutputFromCache)
            {
                echo $strResponse;
            }
            else
            {

                // Create the api endpoint handler
                $objHandler = new $GLOBALS['TL_API'][$strApiEndpoint]();

                $strResponse = $objHandler->generate($arrFragments);
                if (is_array($strResponse)) {
                    header("Content-Type: " . $strResponse['type']);
                    $strResponse = $strResponse['data'];
                }

                if ($blnUseCache)
                {
                    // write data into cache
                    \con4gis\CoreBundle\Resources\contao\classes\C4GApiCache::putCacheData($strApiEndpoint, $arrFragments, $strResponse);
                }

                echo $strResponse;

            }

            // check for jsonp request
            if (\Input::get('callback'))
            {
                echo ');';
            }

        }

        /**
         * Split the request into fragments and find the api resource
         */
        protected function getFragmentsFromUrl()
        {

            // Return null on empty request path
            if (\Environment::get('request') == '') {
                return null;
            }

            $test = \Environment::get('request');

            // Get the request string without the index.php fragment
            if (\Environment::get('request') == $this->_sApiUrl . 'index.php') {
                $strRequest = '';
            } else {
                list($strRequest) = explode('?', str_replace($this->_sApiUrl . 'index.php/', '', \Environment::get('request')), 2);
            }

            // Remove api fragment
            if (substr($strRequest, 0, strlen($this->_sApiUrl)) == $this->_sApiUrl) {
                $strRequest = substr($strRequest, strlen($this->_sApiUrl));
            }

            // URL decode here
            $strRequest = rawurldecode($strRequest);
            $strRequest = substr($strRequest,1);

            // return the fragments
            return explode('/', $strRequest);
        }
    }

    /**
     * Instantiate controller
     */
    $objApi = new Api4Gis();
    $objApi->run();


<?php
/*
 * This file is part of con4gis,
 * the gis-kit for Contao CMS.
 *
 * @package    con4gis
 * @version    6
 * @author     con4gis contributors (see "authors.txt")
 * @license    LGPL-3.0-or-later
 * @copyright  Küstenschmiede GmbH Software & Design
 * @link       https://www.con4gis.org
 */

namespace con4gis\CoreBundle\Resources\contao\classes\container;

class C4GContainer extends C4GBaseContainer
{
    public function addElement($element, $key = null) {
        if (is_array($element)) {
            throw new \Exception('C4GContainer instances may not take arrays or objects other than C4GContainer instances as elements.');
        } elseif (is_object($element)) {
            if (!$element instanceof self) {
                throw new \Exception('C4GContainer instances may not take arrays or objects other than C4GContainer instances as elements.');
            } elseif ($element === $this) {
                throw new \Exception('C4GContainer instances may not take themselves as elements.');
            }
        }
        return $this->add($element, $key);
    }

    public function deleteElement($key) {
        return $this->delete($key);
    }

    /**
     * Add a one-dimensional array to the container, where every array element becomes a container element.
     * @param array $array
     * @throws \Exception
     */
    public function addElementsFromArray(array $array) {
        foreach ($array as $key => $value) {
            $this->addElement($value);
        }
    }

    /**
     * Add a two-dimensional array to the container, where every outer array element becomes a container
     *  and every inner array element becomes an element of the corresponding container.
     * @param array $array
     * @throws \Exception
     */
    public function addContainersFromArray(array $array) {
        foreach ($array as $value) {
            $container = new C4GContainer();
            foreach ($value as $k => $v) {
                $container->addElement($v, $k);
            }
            $this->addElement($container);
        }
    }

    public function hasSameContentAs(C4GContainer $container) {
        foreach ($container as $key => $value) {
            if ($this->containsKey($key) === false) {
                return false;
            }
            if ($value instanceof C4GContainer) {
                if ($value->hasSameContentAs($this->getByKey($key)) === false) {
                    return false;
                }
            } else {
                if ($value !== $this->getByKey($key)) {
                    return false;
                }
            }
        }
        foreach ($this as $key => $value) {
            if ($container->containsKey($key) === false) {
                return false;
            }
        }
        return true;
    }
}


<?php
/*
 * This file is part of con4gis,
 * the gis-kit for Contao CMS.
 *
 * @package    con4gis
 * @version    6
 * @author     con4gis contributors (see "authors.txt")
 * @license    LGPL-3.0-or-later
 * @copyright  Küstenschmiede GmbH Software & Design
 * @link       https://www.con4gis.org
 */
namespace con4gis\CoreBundle\Classes\Helper;

/**
 * Class StringHelper
 * @package con4gis\CoreBundle\Classes\Helper
 */
class StringHelper
{
    /**
     * Ersetzt die nicht erlaubten Zeichen der übergebenen Zeichenkette (Whitelist-Filterung).
     * @param        $string
     * @param string $allowedSigns
     * @return null|string|string[]
     */
    public function removeSpecialSigns($string, $allowedSigns = '')
    {
        // Erlaubte Zeichen festlegen:
        $defaultSigns = 'a-zA-Z0-9' . preg_quote(" üöäÜÖÄß!§$%&/()=?`}][{@<>;:_,.-#'+*\\àÀèÈòÒùÙ");
        $allowedSigns = ($allowedSigns) ? $allowedSigns : $defaultSigns;

        // String filtern:
        $string = preg_replace("|[^" . $allowedSigns . "]|", '', $string);

        return $string;
    }
}


<?php

namespace Saritasa\DingoApi\Paging;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Facades\DB;
use Saritasa\Database\Eloquent\Utils\Query;

class CursorQueryBuilder
{
    protected $cursorRequest;
    protected $model;

    /**
     * Query builder, that returns full set of data to be paginated
     *
     * @var EloquentBuilder|QueryBuilder $query
     */
    protected $originalQuery;
    protected $idKey = CursorResultAuto::ROW_NUM_COLUMN;

    /**
     * Wrap the query to support cursor pagination with custom sort.
     *
     * @param CursorRequest $cursorRequest Requested cursor parameters
     * @param EloquentBuilder|QueryBuilder $query Query builder, that returns full set of data to be paginated
     */
    public function __construct(CursorRequest $cursorRequest, $query)
    {
        $this->cursorRequest = $cursorRequest;
        $this->model = $query instanceof EloquentBuilder
            ? $query->getModel()
            : new class extends Model {
                protected $table = 'fake_model_table';
                protected $primaryKey = CursorResultAuto::ROW_NUM_COLUMN;
            };
        $this->originalQuery = Query::getBaseQuery($query);
    }

    /**
     * Return
     *
     * @return CursorResult
     */
    public function getCursor(): CursorResult
    {
        $query = $this->buildQuery();
        $items = $query->get();
        return new CursorResultAuto($this->cursorRequest, $items);
    }

    /**
     * Build a query, which will return only one page from original data set, matching cursor parameters
     *
     * @return EloquentBuilder|QueryBuilder
     */
    public function buildQuery()
    {
        $wrappedQuery = $this->wrapWithRowCounter($this->originalQuery);
        $query = $this->getFakeModelQuery($wrappedQuery);

        $query->where($this->idKey, '>', $this->cursorRequest->current)
            ->take($this->cursorRequest->pageSize);
        return $query;
    }

    /**
     * Add statement, which will include number of rows in original data set into query, which returns one page
     *
     * @param EloquentBuilder|QueryBuilder $originalQuery Query, that retuns original data set to paginate
     * @return QueryBuilder
     */
    protected function wrapWithRowCounter($originalQuery)
    {
        $tmpQuery = $originalQuery->cloneWithoutBindings(['where'])
            ->crossJoin(DB::raw('(SELECT @row := 0) as row_id_fake_table'));

        return DB::table(
            DB::raw("(SELECT *, (@row := @row+1) as {$this->idKey} FROM (" . $tmpQuery->toSql() . ") as t1) as t2")
        )->mergeBindings($originalQuery);
    }

    /**
     * Buld query around model clone without scopes, that may affect query result.
     *
     * @param QueryBuilder $wrappedQuery Query to actually perform
     * @return EloquentBuilder|QueryBuilder
     */
    protected function getFakeModelQuery($wrappedQuery)
    {
        /**
         * Clone of original Eloquent model, which we can modify to apply some hacks to build specific query
         *
         * @var Model $fakeModel
         */
        $fakeModel = new $this->model;
        $fakeModel->setTable(DB::raw("(" . $wrappedQuery->toSql() . ") AS " . $this->model->getTable()));

        /**
         * Query builder
         *
         * @var EloquentBuilder $modelQuery
         */
        $modelQuery = $fakeModel->newQueryWithoutScopes();
        $builder = Query::getBaseQuery($modelQuery);
        $builder->columns = null; // We always select everything from subquery
        $modelQuery->setQuery($builder->cloneWithoutBindings(['select', 'where'])); // remove default bindings
        $modelQuery->mergeBindings($wrappedQuery);
        return $modelQuery;
    }
}


<?php

/*
 * This file is part of con4gis,
 * the gis-kit for Contao CMS.
 *
 * @package    con4gis
 * @version    6
 * @author     con4gis contributors (see "authors.txt")
 * @license    LGPL-3.0-or-later
 * @copyright  Küstenschmiede GmbH Software & Design
 * @link       https://www.con4gis.org
 */

namespace con4gis\CoreBundle\Resources\contao\classes;

use Contao\LayoutModel;

/**
 * Class ResourceLoader
 * @package c4g\Core
 */
class ResourceLoader
{
    const JAVASCRIPT = 'TL_JAVASCRIPT';
    const CSS = 'TL_CSS';
    const HEAD = 'TL_HEAD';
    const BODY = 'TL_BODY';
    const JQ_UI = 'c4g_jquery_ui';

    /**
     * @param $jsFile
     * @param string $location
     * @param string $key
     */
    public static function loadJavaScriptResource($jsFile, $location = self::JAVASCRIPT, $key = '') {
        switch ($location) {
            case self::JAVASCRIPT:
                if ($key === '') {
                    $GLOBALS[self::JAVASCRIPT][] = $jsFile;
                } else {
                    $GLOBALS[self::JAVASCRIPT][$key] = $jsFile;
                }
                break;
            case self::HEAD:
                if ($key === '') {
                    $GLOBALS[self::HEAD][] = '<script src="' . $jsFile . '" defer></script>' . "\n";
                } else {
                    $GLOBALS[self::HEAD][$key] = '<script src="' . $jsFile . '" defer></script>' . "\n";
                }
                break;
            case self::BODY:
                if ($key === '') {
                    $GLOBALS[self::BODY][] = '<script src="' . $jsFile . '" defer></script>' . "\n";
                } else {
                    $GLOBALS[self::BODY][$key] = '<script src="' . $jsFile . '" defer></script>' . "\n";
                }
                break;
            default:
                break;
        }
    }

    /**
     * @param $jsFile
     * @param string $key
     */
    public static function loadJavaScriptResourceModule($jsFile, $key = '') {
        if ($key === '') {
            $GLOBALS[self::BODY][] = '<script src="' . $jsFile . '" type="module"></script>' . "\n";
        } else {
            $GLOBALS[self::BODY][$key] = '<script src="' . $jsFile . '" type="module"></script>' . "\n";
        }
    }

    /**
     * @param $code
     * @param string $location
     * @param string $key
     */
    public static function loadJavaScriptResourceTag($code, $location = self::HEAD, $key = '') {
        switch ($location) {
            case self::HEAD:
                if ($key === '') {
                    $GLOBALS[self::HEAD][] = "<script>$code</script>\n";
                } else {
                    $GLOBALS[self::HEAD][$key] = "<script>$code</script>\n";
                }
                break;
            case self::BODY:
                if ($key === '') {
                    $GLOBALS[self::BODY][] = "<script>$code</script>\n";
                } else {
                    $GLOBALS[self::BODY][$key] = "<script>$code</script>\n";
                }
                break;
            default:
                break;
        }
    }

    /**
     * @param $cssFile
     * @param string $key
     */
    public static function loadCssResource($cssFile, $key = '') {
        if ($key === '') {
            $GLOBALS[self::CSS][] = $cssFile;
        } else {
            $GLOBALS[self::CSS][$key] = $cssFile;
        }
    }

    /**
     * There is no HTML solution to load CSS deferred. This will actually add a JS block that will load the css when
     *  the page is loaded. Be careful with this because it can make the page look terrible before the CSS is actually
     *  loaded.
     * @param $cssFile
     * @param string $key
     */
    public static function loadCssResourceDeferred($cssFile) {
        self::loadJavaScriptResourceTag(
            "window.addEventListener('load', function() {".
                "var link = document.createElement('link');".
                "link.rel = 'stylesheet';".
                "link.href = '$cssFile';".
                "link.type = 'text/css';".
                "var defer = document.getElementsByTagName('link')[0];" .
                "defer.parentNode.insertBefore(link, defer);" .
//                "document.head.appendChild(link);".
            "});"
        );
    }

    /**
     * @param $location
     * @param $key
     * @return bool
     */
    public static function isResourceLoaded($location, $key) {
        return isset($GLOBALS[$location][$key]);
    }

    /**
     * @param $theme
     */
    public static function loadJqueryUiTheme($theme) {
        self::loadCssResourceDeferred("bundles/con4giscore/vendor/jQuery/ui-themes/themes/$theme/jquery-ui.css");
//        self::loadCssResource("bundles/con4giscore/vendor/jQuery/ui-themes/themes/$theme/jquery-ui.css", self::JQ_UI);
    }

    /**
     * @return bool
     */
    public static function isJqueryUiThemeLoaded() {
        return self::isResourceLoaded(self::CSS, self::JQ_UI);
    }

    /**
     * Function loadResourcesForModule
     *
     * Loads core-resources needed by the given module
     * @deprecated
     */
    public static function loadResourcesForModule($module)
    {

        global $objPage;

        $neededResources = array();

        switch ($module) {
            case 'maps':
                // Maps 3
                //
                $neededResources['clipboard'] = true;

                // check if jQuery needs to be loaded
                $jQueryLoaded = false;
                $scripts = $GLOBALS['TL_JAVASCRIPT'];
                if (is_array($scripts)) {
                    foreach ($scripts as $strScriptUrl) {
                        if (preg_match('/assets\/jquery\/core\/\d+\.\d+\.\d+\/jquery\.min\.js/i', $strScriptUrl)) {
                            $jQueryLoaded = true;
                            break;
                        }
                    }
                }

                if ($objPage->hasJQuery)
                {
                    $jQueryLoaded = true;
                }

                if ($GLOBALS['CON4GIS']['JQUERY-LOADED'])
                {
                    $jQueryLoaded = true;
                }

                $neededResources['jquery'] = !$jQueryLoaded;

                // Load magnific-popup.js for projects
                $neededResources['magnific-popup'] = $GLOBALS['con4gis']['projects']['installed'];

                //ToDo switch for pdf export
                $neededResources['jspdf'] = false;

                break;

            default:
                return false;
        }

        return self::loadResources($neededResources);
    }

    /**
     * Function loadResources
     *
     * Loads the requested resources
     * @deprecated
     */
    public static function loadResources($resources=array())
    {
        if (!is_array($resources) || empty($resources)) {
            $allByDefault = true;
            $resources = array();
        } else {
            $allByDefault = false;
        }

        $resources = array_merge(array
        (
            'jquery' => $allByDefault,
            'magnific-popup' => $allByDefault,
            'clipboard' => $allByDefault,
            'jspdf' => $allByDefault,
        ),
        $resources);

        global $objPage;

        if ($resources['jquery']) {
            // load jQuery

            //workaround hasJQuery param with contao >= 4.5
            if ($objPage->layout) {
                $objLayout = LayoutModel::findByPk($objPage->layout);
                $objPage->hasJQuery = $objLayout->addJQuery;
            }

            if ($objPage->hasJQuery)
            {
                // jQuery is already loaded by Contao, don't load again!
            }
            else {
                self::loadJavaScriptRessource('c4g_jquery', 'assets/jquery/js/jquery.min.js', true);
            }
        }
        self::loadJavaScriptRessource('ajax-request', 'bundles/con4giscore/js/C4GAjaxRequest.js', true);
        if ($resources['magnific-popup']) {
            // load magnific-popup
            self::loadJavaScriptRessource('magnific-popup', 'bundles/con4giscore/vendor/magnific-popup/jquery.magnific-popup.min.js', true);
            self::loadCssRessource('magnific-popup', 'bundles/con4giscore/vendor/magnific-popup/magnific-popup.css');

        }
        if ($resources['clipboard']) {
            // load clipboard
            self::loadJavaScriptRessource('clipboard', 'bundles/con4giscore/vendor/clipboard.min.js', true);
        }
        if ($resources['jspdf']) {
            // load clipboard
            self::loadJavaScriptRessource('jspdf', 'bundles/con4giscore/vendor/jspdf/jspdf.min.js', true);
            //$GLOBALS['TL_JAVASCRIPT']['jspdf.plugin.from_html'] = 'bundles/con4giscore/vendor/jspdf/plugins/from_html.js|static';
        }

        return true;
    }

    /**
     * @param $key
     * @param $cssFile
     * @deprecated
     */
    public static function loadCssRessource($key, $cssFile) {
        self::loadCssResource($cssFile, $key);
    }

    /**
     * @param $key
     * @param $jsFile
     * @param bool $inHeader
     * @param bool $es6Module
     * @deprecated
     */
    public static function loadJavaScriptRessource($key, $jsFile, $inHeader = false, $es6Module = false) {
        if ($inHeader) {
            self::loadJavaScriptResource($jsFile, self::HEAD, $key);
        } else {
            if ($es6Module) {
                self::loadJavaScriptResourceModule($jsFile, $key);
            } else {
                self::loadJavaScriptResource($jsFile, self::BODY, $key);
            }
        }

    }

    /**
     * @param $key
     * @param $jsFile
     * @deprecated
     */
    public static function loadJavaScriptModule($key, $jsFile)
    {
        self::loadJavaScriptResourceModule($jsFile, $key);
    }

    /**
     * @param $key
     * @param $jsFile
     * @deprecated
     */
    public static function loadJavaScriptDeferred($key, $jsFile)
    {
        self::loadJavaScriptResource($jsFile, self::BODY, $key);
    }

    public static function removeJavaScriptRessource($key)
    {
        unset($GLOBALS['TL_BODY'][$key]);
    }
}


<?php

/*
 * This file is part of con4gis,
 * the gis-kit for Contao CMS.
 *
 * @package    con4gis
 * @version    6
 * @author     con4gis contributors (see "authors.txt")
 * @license    LGPL-3.0-or-later
 * @copyright  Küstenschmiede GmbH Software & Design
 * @link       https://www.con4gis.org
 */

namespace con4gis\CoreBundle\Resources\contao\classes;

/**
 * Class C4GHTMLFactory
 * @package c4g
 */
class C4GHTMLFactory
{
	protected $defaultInputClass 			= 'c4gInput';
	protected $defaultDivClass 				= 'c4gDiv';
	protected $defaultButtonClass 			= 'c4gButton';

	/**
	 * inserts an
-tag
	 * @return string
	 */
	public static function lineBreak()
	{
		return '
';
	}

	/**
	 * inserts an
-tag
	 * @return string
	 */
	public static function horizontalLine()
	{
		return '
';
	}

	/**
	 * wrapps a string with HTML-headline-tags
	 * @param  string  $content
	 * @param  integer $type
	 * @return string
	 */
	public static function headline( $content, $type = 1 )
	{
		return '<h' . $type . '>' . $content . '</h' . $type . '>';
	}

	/**
	 * wrapps a string with
-tags
	 * @param  string $content
	 * @param  string $id
	 * @param  string $class
	 * @return string
	 */
	public static function paragraph( $content, $id = '', $class = '' )
	{
		$attr = '';
		if (!empty( $id )) {
			$attr .= ' id="' . $id . '"';
		}
		if (!empty( $class )) {
			$attr .= ' class="' . $class . '"';
		}
		return '
' . $content . '
';
	}

	/**
	 * wrapps a string with -tags
	 * @param  string $content
	 * @param  string $id
	 * @param  string $class
	 * @return string
	 */
	public static function span( $content, $id='', $class='' )
	{
		$attr = '';
		if (!empty( $id )) {
			$attr .= ' id="' . $id . '"';
		}
		if (!empty( $class )) {
			$attr .= ' class="' . $class . '"';
		}
		return '' . $content . '';
	}

	/**
	 * [imgLink description]
	 * @param  array  $options [description]
	 * @return string          [description]
	 */
	public static function imgLink( $options=array() )
	{
		$options['href'] = $options['href'] ?: '#';
		$options['addClass'] = $options['addClass'] ?: '';
		$options['target'] = $options['target'] ? ' target="' . $options['target'] . '"' : '';

		if ($options['c4gImg']) {
			$options['src'] = 'bundles/con4giscore/images/' . $options['c4gImg'] . '.png';
		} else {
			$options['src'] = $options['src'] ?: 'bundles/con4giscore/images/href.png';
		}

		$options['imgWidth'] = $options['imgWidth'] ?: '75';
		$options['imgHeight'] = $options['imgHeight'] ?: '75';

		$options['label'] = $options['label'] ? '' . $options['label'] . '' : '';

		return ''.
				''.
				$options['label'] . '';
	}

	public static function c4gGuiButton( $label, $action, $additionalClass='', $accesskey='' )
	{
		return '' . $label . '';
	}

	// ----------------------------------------------------------------------------------
	// comming soon
	// ----------------------------------------------------------------------------------
	public static function textfield(  )
	{
		//with label
	}

	public static function checkbox( )
	{
		//PASS
	}

	public static function radiobutton( )
	{
		//PASS
	}

	public static function dropdown( )
	{
		//PASS
	}

	public static function unsortedList( )
	{
		//PASS
	}
}?>