<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\Image;

use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\Image\ImageInterface;
use Contao\Image\Picture;
use Contao\Image\PictureConfiguration;
use Contao\Image\PictureConfigurationInterface;
use Contao\Image\PictureConfigurationItem;
use Contao\Image\PictureGeneratorInterface;
use Contao\Image\PictureInterface;
use Contao\Image\ResizeConfiguration;
use Contao\Image\ResizeConfigurationInterface;
use Contao\Image\ResizeOptions;
use Contao\ImageSizeItemModel;
use Contao\ImageSizeModel;

class PictureFactory implements PictureFactoryInterface
{
    /**
     * @var PictureGeneratorInterface
     */
    private $pictureGenerator;

    /**
     * @var ImageFactoryInterface
     */
    private $imageFactory;

    /**
     * @var ContaoFramework
     */
    private $framework;

    /**
     * @var bool
     */
    private $bypassCache;

    /**
     * @var array
     */
    private $imagineOptions;

    /**
     * @var string
     */
    private $defaultDensities = '';

    public function __construct(PictureGeneratorInterface $pictureGenerator, ImageFactoryInterface $imageFactory, ContaoFramework $framework, bool $bypassCache, array $imagineOptions)
    {
        $this->pictureGenerator = $pictureGenerator;
        $this->imageFactory = $imageFactory;
        $this->framework = $framework;
        $this->bypassCache = $bypassCache;
        $this->imagineOptions = $imagineOptions;
    }

    /**
     * {@inheritdoc}
     */
    public function setDefaultDensities($densities): self
    {
        $this->defaultDensities = (string) $densities;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function create($path, $size = null): PictureInterface
    {
        $attributes = [];

        if ($path instanceof ImageInterface) {
            $image = $path;
        } else {
            $image = $this->imageFactory->create($path);
        }

        if (\is_array($size) && isset($size[2]) && \is_string($size[2]) && 1 === substr_count($size[2], '_')) {
            $image->setImportantPart($this->imageFactory->getImportantPartFromLegacyMode($image, $size[2]));
            $size[2] = ResizeConfigurationInterface::MODE_CROP;
        }

        if ($size instanceof PictureConfigurationInterface) {
            $config = $size;
        } else {
            [$config, $attributes] = $this->createConfig($size);
        }

        $picture = $this->pictureGenerator->generate(
            $image,
            $config,
            (new ResizeOptions())->setImagineOptions($this->imagineOptions)->setBypassCache($this->bypassCache)
        );

        return $this->addImageAttributes($picture, $attributes);
    }

    /**
     * Creates a picture configuration.
     *
     * @param int|array|null $size
     *
     * @return (PictureConfiguration|array<string,string>)[]
     */
    private function createConfig($size): array
    {
        if (!\is_array($size)) {
            $size = [0, 0, $size];
        }

        $config = new PictureConfiguration();
        $attributes = [];

        if (!isset($size[2]) || !is_numeric($size[2])) {
            $resizeConfig = new ResizeConfiguration();

            if (!empty($size[0])) {
                $resizeConfig->setWidth((int) $size[0]);
            }

            if (!empty($size[1])) {
                $resizeConfig->setHeight((int) $size[1]);
            }

            if (!empty($size[2])) {
                $resizeConfig->setMode($size[2]);
            }

            $configItem = new PictureConfigurationItem();
            $configItem->setResizeConfig($resizeConfig);

            if ($this->defaultDensities) {
                $configItem->setDensities($this->defaultDensities);
            }

            $config->setSize($configItem);

            return [$config, $attributes];
        }

        /** @var ImageSizeModel $imageSizeModel */
        $imageSizeModel = $this->framework->getAdapter(ImageSizeModel::class);
        $imageSizes = $imageSizeModel->findByPk($size[2]);

        $config->setSize($this->createConfigItem($imageSizes));

        if ($imageSizes && $imageSizes->cssClass) {
            $attributes['class'] = $imageSizes->cssClass;
        }

        /** @var ImageSizeItemModel $imageSizeItemModel */
        $imageSizeItemModel = $this->framework->getAdapter(ImageSizeItemModel::class);
        $imageSizeItems = $imageSizeItemModel->findVisibleByPid($size[2], ['order' => 'sorting ASC']);

        if (null !== $imageSizeItems) {
            $configItems = [];

            foreach ($imageSizeItems as $imageSizeItem) {
                $configItems[] = $this->createConfigItem($imageSizeItem);
            }

            $config->setSizeItems($configItems);
        }

        return [$config, $attributes];
    }

    /**
     * Creates a picture configuration item.
     *
     * @param ImageSizeModel|ImageSizeItemModel|null $imageSize
     */
    private function createConfigItem($imageSize): PictureConfigurationItem
    {
        $configItem = new PictureConfigurationItem();
        $resizeConfig = new ResizeConfiguration();

        if (null !== $imageSize) {
            $resizeConfig
                ->setWidth($imageSize->width)
                ->setHeight($imageSize->height)
                ->setMode($imageSize->resizeMode)
                ->setZoomLevel($imageSize->zoom)
            ;

            $configItem
                ->setResizeConfig($resizeConfig)
                ->setSizes($imageSize->sizes)
                ->setDensities($imageSize->densities)
            ;

            if (isset($imageSize->media)) {
                $configItem->setMedia($imageSize->media);
            }
        }

        return $configItem;
    }

    private function addImageAttributes(PictureInterface $picture, array $attributes): PictureInterface
    {
        if (empty($attributes)) {
            return $picture;
        }

        $img = $picture->getImg();

        foreach ($attributes as $attribute => $value) {
            $img[$attribute] = $value;
        }

        return new Picture($img, $picture->getSources());
    }
}


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Provide methods to handle image size fields.
 *
 * @property integer $maxlength
 * @property array   $options
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class ImageSize extends Widget
{

	/**
	 * Submit user input
	 * @var boolean
	 */
	protected $blnSubmitInput = true;

	/**
	 * Template
	 * @var string
	 */
	protected $strTemplate = 'be_widget';

	/**
	 * Available options
	 * @var array
	 */
	protected $arrAvailableOptions = array();

	/**
	 * Add specific attributes
	 *
	 * @param string $strKey
	 * @param mixed  $varValue
	 */
	public function __set($strKey, $varValue)
	{
		switch ($strKey)
		{
			case 'maxlength':
				if ($varValue > 0)
				{
					$this->arrAttributes['maxlength'] = $varValue;
				}
				break;

			case 'options':
				$this->arrOptions = StringUtil::deserialize($varValue);
				break;

			default:
				parent::__set($strKey, $varValue);
				break;
		}
	}

	/**
	 * Trim values
	 *
	 * @param mixed $varInput
	 *
	 * @return mixed
	 */
	protected function validator($varInput)
	{
		$varInput[2] = preg_replace('/[^a-z0-9_]+/', '', $varInput[2]);

		if (!is_numeric($varInput[2]))
		{
			switch ($varInput[2])
			{
				// Validate relative dimensions - width or height required
				case 'proportional':
				case 'box':
					$this->mandatory = !$varInput[0] && !$varInput[1];
					break;

				// Validate exact dimensions - width and height required
				case 'crop':
				case 'left_top':
				case 'center_top':
				case 'right_top':
				case 'left_center':
				case 'center_center':
				case 'right_center':
				case 'left_bottom':
				case 'center_bottom':
				case 'right_bottom':
					$this->mandatory = !$varInput[0] || !$varInput[1];
					break;
			}

			$varInput[0] = parent::validator($varInput[0]);
			$varInput[1] = parent::validator($varInput[1]);
		}

		$this->import(BackendUser::class, 'User');

		$imageSizes = System::getContainer()->get('contao.image.image_sizes');
		$this->arrAvailableOptions = $this->User->isAdmin ? $imageSizes->getAllOptions() : $imageSizes->getOptionsForUser($this->User);

		if (!$this->isValidOption($varInput[2]))
		{
			$this->addError(sprintf($GLOBALS['TL_LANG']['ERR']['invalid'], $varInput[2]));
		}

		return $varInput;
	}

	/**
	 * Check whether an input is one of the given options
	 *
	 * @param mixed $varInput The input string or array
	 *
	 * @return boolean True if the selected option exists
	 */
	protected function isValidOption($varInput)
	{
		if ($varInput == '')
		{
			return true;
		}

		foreach ($this->arrAvailableOptions as $strGroup=>$arrValues)
		{
			if ($strGroup == 'image_sizes')
			{
				if (isset($arrValues[$varInput]))
				{
					return true;
				}
			}
			elseif (\in_array($varInput, $arrValues))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Generate the widget and return it as string
	 *
	 * @return string
	 */
	public function generate()
	{
		if (!\is_array($this->varValue))
		{
			$this->varValue = array($this->varValue);
		}

		$arrFields = array();
		$arrOptions = array();

		foreach ($this->arrOptions as $strKey=>$arrOption)
		{
			if (isset($arrOption['value']))
			{
				$arrOptions[] = sprintf('<option value="%s"%s>%s</option>',
									   StringUtil::specialchars($arrOption['value']),
									   $this->optionSelected($arrOption['value'], $this->varValue[2]),
									   $arrOption['label']);
			}
			else
			{
				$arrOptgroups = array();

				foreach ($arrOption as $arrOptgroup)
				{
					$arrOptgroups[] = sprintf('<option value="%s"%s>%s</option>',
											   StringUtil::specialchars($arrOptgroup['value']),
											   $this->optionSelected($arrOptgroup['value'], $this->varValue[2]),
											   $arrOptgroup['label']);
				}

				$arrOptions[] = sprintf('<optgroup label=" %s">%s</optgroup>', StringUtil::specialchars($strKey), implode('', $arrOptgroups));
			}
		}

		$arrFields[] = sprintf('<select name="%s[2]" id="ctrl_%s" class="tl_select_interval" onfocus="Backend.getScrollOffset()"%s>%s</select>',
								$this->strName,
								$this->strId.'_3',
								$this->getAttribute('disabled'),
								implode(' ', $arrOptions));

		for ($i=0; $i<2; $i++)
		{
			$arrFields[] = sprintf('<input type="text" name="%s[%s]" id="ctrl_%s" class="tl_text_4 tl_imageSize_%s" value="%s"%s onfocus="Backend.getScrollOffset()">',
									$this->strName,
									$i,
									$this->strId.'_'.$i,
									$i,
									StringUtil::specialchars(@$this->varValue[$i]), // see #4979
									$this->getAttributes());
		}

		return sprintf('
%s
%s',
						$this->strId,
						(($this->strClass != '') ? ' ' . $this->strClass : ''),
						implode(' ', $arrFields),
						$this->wizard);
	}
}

class_alias(ImageSize::class, 'ImageSize');


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\Util;

use Symfony\Component\Filesystem\Filesystem;

class SymlinkUtil
{
    /**
     * Generates a symlink.
     *
     * The method will try to generate relative symlinks and fall back to generating
     * absolute symlinks if relative symlinks are not supported (see #208).
     */
    public static function symlink(string $target, string $link, string $rootDir): void
    {
        static::validateSymlink($target, $link, $rootDir);

        $fs = new Filesystem();

        if (!$fs->isAbsolutePath($target)) {
            $target = $rootDir.'/'.$target;
        }

        if (!$fs->isAbsolutePath($link)) {
            $link = $rootDir.'/'.$link;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $fs->symlink($target, $link);
        } else {
            $fs->symlink(rtrim($fs->makePathRelative($target, \dirname($link)), '/'), $link);
        }
    }

    /**
     * Validates a symlink.
     *
     * @throws \InvalidArgumentException
     * @throws \LogicException
     */
    public static function validateSymlink(string $target, string $link, string $rootDir): void
    {
        if ('' === $target) {
            throw new \InvalidArgumentException('The symlink target must not be empty.');
        }

        if ('' === $link) {
            throw new \InvalidArgumentException('The symlink path must not be empty.');
        }

        if (false !== strpos($link, '../')) {
            throw new \InvalidArgumentException('The symlink path must not be relative.');
        }

        $fs = new Filesystem();

        if ($fs->exists($rootDir.'/'.$link) && !is_link($rootDir.'/'.$link)) {
            throw new \LogicException(sprintf('The path "%s" exists and is not a symlink.', $link));
        }
    }
}


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

use Contao\Model\Collection;

/**
 * Reads and writes newsletter recipients
 *
 * @property integer $id
 * @property integer $pid
 * @property integer $tstamp
 * @property string  $email
 * @property boolean $active
 * @property string  $source
 * @property string  $addedOn
 *
 * @method static NewsletterRecipientsModel|null findById($id, array $opt=array())
 * @method static NewsletterRecipientsModel|null findByPk($id, array $opt=array())
 * @method static NewsletterRecipientsModel|null findByIdOrAlias($val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneBy($col, $val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneByPid($val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneByTstamp($val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneByEmail($val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneByActive($val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneBySource($val, array $opt=array())
 * @method static NewsletterRecipientsModel|null findOneByAddedOn($val, array $opt=array())
 *
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findByPid($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findByTstamp($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findByEmail($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findByActive($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findBySource($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findByAddedOn($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findMultipleByIds($val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findBy($col, $val, array $opt=array())
 * @method static Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null findAll(array $opt=array())
 *
 * @method static integer countById($id, array $opt=array())
 * @method static integer countByPid($val, array $opt=array())
 * @method static integer countByTstamp($val, array $opt=array())
 * @method static integer countByEmail($val, array $opt=array())
 * @method static integer countByActive($val, array $opt=array())
 * @method static integer countBySource($val, array $opt=array())
 * @method static integer countByAddedOn($val, array $opt=array())
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class NewsletterRecipientsModel extends Model
{

	/**
	 * Table name
	 * @var string
	 */
	protected static $strTable = 'tl_newsletter_recipients';

	/**
	 * Find recipients by their e-mail address and parent ID
	 *
	 * @param string $strEmail   The e-mail address
	 * @param array  $arrPids    An array of newsletter channel IDs
	 * @param array  $arrOptions An optional options array
	 *
	 * @return Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null A collection of models or null if there are no recipients
	 */
	public static function findByEmailAndPids($strEmail, $arrPids, array $arrOptions=array())
	{
		if (empty($arrPids) || !\is_array($arrPids))
		{
			return null;
		}

		$t = static::$strTable;

		return static::findBy(array("$t.email=? AND $t.pid IN(" . implode(',', array_map('\intval', $arrPids)) . ")"), $strEmail, $arrOptions);
	}

	/**
	 * Find old subscriptions by e-mail address and channels
	 *
	 * @param string $strEmail   The e-mail address
	 * @param array  $arrPids    An array of newsletter channel IDs
	 * @param array  $arrOptions An optional options array
	 *
	 * @return Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null A collection of models or null if there are no old subscriptions
	 */
	public static function findOldSubscriptionsByEmailAndPids($strEmail, $arrPids, array $arrOptions=array())
	{
		if (empty($arrPids) || !\is_array($arrPids))
		{
			return null;
		}

		$t = static::$strTable;

		return static::findBy(array("$t.email=? AND $t.pid IN(" . implode(',', array_map('\intval', $arrPids)) . ") AND $t.active=''"), $strEmail, $arrOptions);
	}

	/**
	 * Find subscriptions that have not been activated for more than 24 hours
	 *
	 * @param array $arrOptions An optional options array
	 *
	 * @return Collection|NewsletterRecipientsModel[]|NewsletterRecipientsModel|null A collection of models or null if there are no expired subscriptions
	 */
	public static function findExpiredSubscriptions(array $arrOptions=array())
	{
		$t = static::$strTable;
		$objDatabase = Database::getInstance();

		$objResult = $objDatabase->prepare("SELECT * FROM $t WHERE active='' AND EXISTS (SELECT * FROM tl_opt_in_related r LEFT JOIN tl_opt_in o ON r.pid=o.id WHERE r.relTable='$t' AND r.relId=$t.id AND o.createdOn<=? AND o.confirmedOn=0)")
								 ->execute(strtotime('-24 hours'));

		if ($objResult->numRows < 1)
		{
			return null;
		}

		return static::createCollectionFromDbResult($objResult, $t);
	}
}

class_alias(NewsletterRecipientsModel::class, 'NewsletterRecipientsModel');


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\InstallationBundle\Database;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\DriverManager;

class ConnectionFactory
{
    /**
     * Returns the database connection object.
     */
    public static function create(array $parameters): ?Connection
    {
        $params = [
            'driver' => 'pdo_mysql',
            'host' => $parameters['parameters']['database_host'],
            'port' => $parameters['parameters']['database_port'],
            'user' => $parameters['parameters']['database_user'],
            'password' => $parameters['parameters']['database_password'],
            'dbname' => $parameters['parameters']['database_name'],
        ];

        try {
            return DriverManager::getConnection($params);
        } catch (DBALException $e) {
            // ignore
        }

        return null;
    }
}


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Front end content element "code".
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class ContentCode extends ContentElement
{

	/**
	 * Template
	 * @var string
	 */
	protected $strTemplate = 'ce_code';

	/**
	 * Show the raw code in the back end
	 *
	 * @return string
	 */
	public function generate()
	{
		if (TL_MODE == 'BE')
		{
			$return = '<pre>'. htmlspecialchars($this->code) .'</pre>';

			if ($this->headline != '')
			{
				$return = '<'. $this->hl .'>'. $this->headline .'</'. $this->hl .'>'. $return;
			}

			return $return;
		}

		return parent::generate();
	}

	/**
	 * Generate the content element
	 */
	protected function compile()
	{
		if ($this->highlight == 'C#')
		{
			$this->highlight = 'csharp';
		}
		elseif ($this->highlight == 'C++')
		{
			$this->highlight = 'cpp';
		}

		$this->Template->code = htmlspecialchars($this->code);
		$this->Template->cssClass = strtolower($this->highlight) ?: 'nohighlight';
	}
}

class_alias(ContentCode::class, 'ContentCode');


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Store\FlockStore;

/**
 * @deprecated Deprecated since Contao 4.7, to be removed in Contao 5.0; use
 *             the lock service instead
 */
abstract class AbstractLockedCommand extends ContainerAwareCommand
{
    /**
     * {@inheritdoc}
     */
    final protected function execute(InputInterface $input, OutputInterface $output): int
    {
        @trigger_error('Using the AbstractLockedCommand has been deprecated and will no longer work in Contao 5.0. Use the lock service instead.', E_USER_DEPRECATED);

        $store = new FlockStore($this->getTempDir());
        $factory = new Factory($store);
        $lock = $factory->createLock($this->getName());

        if (!$lock->acquire()) {
            $output->writeln('The command is already running in another process.');

            return 1;
        }

        if (($errorCode = $this->executeLocked($input, $output)) > 0) {
            $lock->release();

            return $errorCode;
        }

        $lock->release();

        return 0;
    }

    /**
     * @return int
     */
    abstract protected function executeLocked(InputInterface $input, OutputInterface $output);

    /**
     * Creates an installation specific folder in the temporary directory and returns its path.
     */
    private function getTempDir(): string
    {
        $container = $this->getContainer();
        $tmpDir = sys_get_temp_dir().'/'.md5($container->getParameter('kernel.project_dir'));

        if (!is_dir($tmpDir)) {
            $container->get('filesystem')->mkdir($tmpDir);
        }

        return $tmpDir;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\EventListener;

use Contao\CoreBundle\Doctrine\Schema\DcaSchemaProvider;
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Schema\Index;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;

class DoctrineSchemaListener
{
    /**
     * @var DcaSchemaProvider
     */
    private $provider;

    public function __construct(DcaSchemaProvider $provider)
    {
        $this->provider = $provider;
    }

    /**
     * Adds the Contao DCA information to the Doctrine schema.
     */
    public function postGenerateSchema(GenerateSchemaEventArgs $event): void
    {
        $this->provider->appendToSchema($event->getSchema());
    }

    /**
     * Adds the index length on MySQL platforms for Doctrine DBAL <2.9.
     */
    public function onSchemaIndexDefinition(SchemaIndexDefinitionEventArgs $event): void
    {
        // Skip for doctrine/dbal >= 2.9
        if (method_exists(AbstractPlatform::class, 'supportsColumnLengthIndexes')) {
            return;
        }

        $connection = $event->getConnection();

        if (!$connection->getDatabasePlatform() instanceof MySqlPlatform) {
            return;
        }

        $data = $event->getTableIndex();

        // Ignore primary keys
        if ('PRIMARY' === $data['name']) {
            return;
        }

        $columns = [];
        $query = sprintf("SHOW INDEX FROM %s WHERE Key_name='%s'", $event->getTable(), $data['name']);
        $result = $connection->executeQuery($query);

        while ($row = $result->fetch()) {
            if (null !== $row['Sub_part']) {
                $columns[] = sprintf('%s(%s)', $row['Column_name'], $row['Sub_part']);
            } else {
                $columns[] = $row['Column_name'];
            }
        }

        $event->setIndex(
            new Index(
                $data['name'],
                $columns,
                $data['unique'],
                $data['primary'],
                $data['flags'],
                $data['options']
            )
        );

        $event->preventDefault();
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\Monolog;

use Contao\StringUtil;
use Contao\System;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Statement;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\AbstractProcessingHandler;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

class ContaoTableHandler extends AbstractProcessingHandler implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    /**
     * @var string
     */
    private $dbalServiceName = 'doctrine.dbal.default_connection';

    /**
     * @var Statement
     */
    private $statement;

    public function getDbalServiceName(): string
    {
        return $this->dbalServiceName;
    }

    public function setDbalServiceName(string $name): void
    {
        $this->dbalServiceName = $name;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record): bool
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        $record = $this->processRecord($record);
        $record['formatted'] = $this->getFormatter()->format($record);

        if (!isset($record['extra']['contao']) || !($record['extra']['contao'] instanceof ContaoContext)) {
            return false;
        }

        try {
            $this->write($record);
        } catch (\Exception $e) {
            return false;
        }

        $this->executeHook($record['message'], $record['extra']['contao']);

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record): void
    {
        $this->createStatement();

        /** @var \DateTime $date */
        $date = $record['datetime'];

        /** @var ContaoContext $context */
        $context = $record['extra']['contao'];

        $this->statement->execute([
            'tstamp' => $date->format('U'),
            'text' => StringUtil::specialchars((string) $record['formatted']),
            'source' => (string) $context->getSource(),
            'action' => (string) $context->getAction(),
            'username' => (string) $context->getUsername(),
            'func' => $context->getFunc(),
            'browser' => StringUtil::specialchars((string) $context->getBrowser()),
        ]);
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultFormatter(): LineFormatter
    {
        return new LineFormatter('%message%');
    }

    /**
     * Verifies the database connection and prepares the statement.
     *
     * @throws \RuntimeException
     */
    private function createStatement(): void
    {
        if (null !== $this->statement) {
            return;
        }

        if (null === $this->container || !$this->container->has($this->dbalServiceName)) {
            throw new \RuntimeException('The container has not been injected or the database service is missing');
        }

        /** @var Connection $connection */
        $connection = $this->container->get($this->dbalServiceName);

        $this->statement = $connection->prepare('
            INSERT INTO
                tl_log
                    (tstamp, source, action, username, text, func, browser)
                VALUES
                    (:tstamp, :source, :action, :username, :text, :func, :browser)
        ');
    }

    /**
     * Executes the legacy hook if the Contao framework is booted.
     */
    private function executeHook(string $message, ContaoContext $context): void
    {
        if (null === $this->container || !$this->container->has('contao.framework')) {
            return;
        }

        $framework = $this->container->get('contao.framework');

        if (!$this->hasAddLogEntryHook() || !$framework->isInitialized()) {
            return;
        }

        @trigger_error('Using the addLogEntry hook has been deprecated and will no longer work in Contao 5.0.', E_USER_DEPRECATED);

        /** @var System $system */
        $system = $framework->getAdapter(System::class);

        // Must create variables to allow modification-by-reference in hook
        $func = $context->getFunc();
        $action = $context->getAction();

        foreach ($GLOBALS['TL_HOOKS']['addLogEntry'] as $callback) {
            $system->importStatic($callback[0])->{$callback[1]}($message, $func, $action);
        }
    }

    private function hasAddLogEntryHook(): bool
    {
        return !empty($GLOBALS['TL_HOOKS']['addLogEntry']) && \is_array($GLOBALS['TL_HOOKS']['addLogEntry']);
    }
}


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Provide methods to manage front end users.
 *
 * @property array   $allGroups
 * @property string  $loginPage
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class FrontendUser extends User
{

	/**
	 * Symfony Security session key
	 * @var string
	 */
	const SECURITY_SESSION_KEY = '_security_contao_frontend';

	/**
	 * Current object instance (do not remove)
	 * @var FrontendUser
	 */
	protected static $objInstance;

	/**
	 * Name of the corresponding table
	 * @var string
	 */
	protected $strTable = 'tl_member';

	/**
	 * Name of the current cookie
	 * @var string
	 */
	protected $strCookie = 'FE_USER_AUTH';

	/**
	 * Group login page
	 * @var string
	 */
	protected $strLoginPage;

	/**
	 * Groups
	 * @var array
	 */
	protected $arrGroups;

	/**
	 * Symfony security roles
	 * @var array
	 */
	protected $roles = array('ROLE_MEMBER');

	/**
	 * Initialize the object
	 */
	protected function __construct()
	{
		parent::__construct();

		$this->strIp = Environment::get('ip');
		$this->strHash = Input::cookie($this->strCookie);
	}

	/**
	 * Instantiate a new user object
	 *
	 * @return static|User The object instance
	 */
	public static function getInstance()
	{
		if (static::$objInstance !== null)
		{
			return static::$objInstance;
		}

		$objToken = System::getContainer()->get('security.token_storage')->getToken();

		// Load the user from the security storage
		if ($objToken !== null && is_a($objToken->getUser(), static::class))
		{
			return $objToken->getUser();
		}

		// Check for an authenticated user in the session
		$strUser = System::getContainer()->get('contao.security.token_checker')->getFrontendUsername();

		if ($strUser !== null)
		{
			static::$objInstance = static::loadUserByUsername($strUser);

			return static::$objInstance;
		}

		return parent::getInstance();
	}

	/**
	 * Extend parent setter class and modify some parameters
	 *
	 * @param string $strKey
	 * @param mixed  $varValue
	 */
	public function __set($strKey, $varValue)
	{
		switch ($strKey)
		{
			case 'allGroups':
				$this->arrGroups = $varValue;
				break;

			default:
				parent::__set($strKey, $varValue);
				break;
		}
	}

	/**
	 * Extend parent getter class and modify some parameters
	 *
	 * @param string $strKey
	 *
	 * @return mixed
	 */
	public function __get($strKey)
	{
		switch ($strKey)
		{
			case 'allGroups':
				return $this->arrGroups;
				break;

			case 'loginPage':
				return $this->strLoginPage;
				break;
		}

		return parent::__get($strKey);
	}

	/**
	 * Authenticate a user
	 *
	 * @return boolean
	 *
	 * @deprecated Deprecated since Contao 4.5, to be removed in Contao 5.0.
	 *             Use Symfony security instead.
	 */
	public function authenticate()
	{
		@trigger_error('Using FrontendUser::authenticate() has been deprecated and will no longer work in Contao 5.0. Use Symfony security instead.', E_USER_DEPRECATED);

		return System::getContainer()->get('contao.security.token_checker')->hasFrontendUser();
	}

	/**
	 * Try to login the current user
	 *
	 * @return boolean True if the user could be logged in
	 *
	 * @deprecated Deprecated since Contao 4.5, to be removed in Contao 5.0.
	 *             Use Symfony security instead.
	 */
	public function login()
	{
		@trigger_error('Using FrontendUser::login() has been deprecated and will no longer work in Contao 5.0. Use Symfony security instead.', E_USER_DEPRECATED);

		return System::getContainer()->get('contao.security.token_checker')->hasFrontendUser();
	}

	/**
	 * Save the original group membership
	 *
	 * @param string $strColumn
	 * @param mixed  $varValue
	 *
	 * @return boolean
	 */
	public function findBy($strColumn, $varValue)
	{
		if (parent::findBy($strColumn, $varValue) === false)
		{
			return false;
		}

		$this->arrGroups = $this->groups;

		return true;
	}

	/**
	 * Restore the original group membership
	 */
	public function save()
	{
		$groups = $this->groups;
		$this->arrData['groups'] = $this->arrGroups;
		parent::save();
		$this->groups = $groups;
	}

	/**
	 * Set all user properties from a database record
	 */
	protected function setUserFromDb()
	{
		$this->intId = $this->id;

		// Unserialize values
		foreach ($this->arrData as $k=>$v)
		{
			if (!is_numeric($v))
			{
				$this->$k = StringUtil::deserialize($v);
			}
		}

		$GLOBALS['TL_USERNAME'] = $this->username;

		// Make sure that groups is an array
		if (!\is_array($this->groups))
		{
			$this->groups = ($this->groups != '') ? array($this->groups) : array();
		}

		// Skip inactive groups
		if (($objGroups = MemberGroupModel::findAllActive()) !== null)
		{
			$this->groups = array_intersect($this->groups, $objGroups->fetchEach('id'));
		}

		// Get the group login page
		if ($this->groups[0] > 0)
		{
			$objGroup = MemberGroupModel::findPublishedById($this->groups[0]);

			if ($objGroup !== null && $objGroup->redirect && $objGroup->jumpTo)
			{
				$this->strLoginPage = $objGroup->jumpTo;
			}
		}
	}

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

class_alias(FrontendUser::class, 'FrontendUser');


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

use Contao\Image\ImportantPart;
use Contao\Image\PictureConfiguration;
use Contao\Image\PictureConfigurationItem;
use Contao\Image\ResizeConfiguration;
use Contao\Image\ResizeOptions;
use Contao\Model\Collection;
use Imagine\Image\Box;
use Imagine\Image\Point;

@trigger_error('Using the Contao\Picture class has been deprecated and will no longer work in Contao 5.0. Use the contao.image.picture_factory service instead.', E_USER_DEPRECATED);

/**
 * Resizes images and creates picture data
 *
 * The class resizes images and prepares data for the `<picture>` element.
 *
 * Usage:
 *
 *     $picture = new Picture(new File('example.jpg'));
 *
 *     $data = $picture
 *         ->setImportantPart(array('x'=>10, 'y'=>10, 'width'=>100, 'height'=>100))
 *         ->setImageSize(ImageSizeModel::findByPk(1))
 *         ->setImageSizeItems(ImageSizeItemModel::findVisibleByPid(1, array('order'=>'sorting ASC')))
 *         ->getTemplateData()
 *     ;
 *
 *     // Shortcut
 *     $data = Picture::create('example.jpg', 1)->getTemplateData();
 *     $data = Picture::create('example.jpg', array(100, 100, 'crop'))->getTemplateData();
 *
 * @author Martin Auswöger <https://github.com/ausi>
 * @author Yanick Witschi <https://github.com/Toflar>
 *
 * @deprecated Deprecated since Contao 4.3, to be removed in Contao 5.0.
 *             Use the contao.image.picture_factory service instead.
 */
class Picture
{

	/**
	 * The Image instance of the source image
	 *
	 * @var Image
	 */
	protected $image;

	/**
	 * The image size
	 *
	 * @var ImageSizeModel|object
	 */
	protected $imageSize;

	/**
	 * The image size items collection
	 *
	 * @var ImageSizeItemModel[]|Collection
	 */
	protected $imageSizeItems = array();

	/**
	 * Create a new object to handle a picture element
	 *
	 * @param File $file A file instance of the source image
	 */
	public function __construct(File $file)
	{
		$this->image = new Image($file);
	}

	/**
	 * Create a picture instance from the given image path and size
	 *
	 * @param string|File   $file The image path or File instance
	 * @param array|integer $size The image size as array (width, height, resize mode) or an tl_image_size ID
	 *
	 * @return static The created picture instance
	 */
	public static function create($file, $size=null)
	{
		if (\is_string($file))
		{
			$file = new File(rawurldecode($file));
		}

		$imageSize = null;
		$picture = new static($file);

		// tl_image_size ID as resize mode
		if (\is_array($size) && !empty($size[2]) && is_numeric($size[2]))
		{
			$size = (int) $size[2];
		}

		$imageSize = null;

		if (!\is_array($size))
		{
			$imageSize = ImageSizeModel::findByPk($size);

			if ($imageSize === null)
			{
				$size = array();
			}
		}

		if (\is_array($size))
		{
			$size += array(0, 0, 'crop');

			$imageSize = new \stdClass();
			$imageSize->width = $size[0];
			$imageSize->height = $size[1];
			$imageSize->resizeMode = $size[2];
			$imageSize->zoom = 0;
		}

		$picture->setImageSize($imageSize);

		if ($imageSize !== null && !empty($imageSize->id))
		{
			$picture->setImageSizeItems(ImageSizeItemModel::findVisibleByPid($imageSize->id, array('order'=>'sorting ASC')));
		}

		$fileRecord = FilesModel::findByPath($file->path);

		if ($fileRecord !== null && $fileRecord->importantPartWidth && $fileRecord->importantPartHeight)
		{
			$picture->setImportantPart(array
			(
				'x' => (int) $fileRecord->importantPartX,
				'y' => (int) $fileRecord->importantPartY,
				'width' => (int) $fileRecord->importantPartWidth,
				'height' => (int) $fileRecord->importantPartHeight,
			));
		}

		return $picture;
	}

	/**
	 * Set the important part settings
	 *
	 * @param array $importantPart The settings array
	 *
	 * @return $this The picture object
	 */
	public function setImportantPart(array $importantPart = null)
	{
		$this->image->setImportantPart($importantPart);

		return $this;
	}

	/**
	 * Set the image size
	 *
	 * @param ImageSizeModel|object $imageSize The image size
	 *
	 * @return $this The picture object
	 */
	public function setImageSize($imageSize)
	{
		$this->imageSize = $imageSize;

		return $this;
	}

	/**
	 * Set the image size items collection
	 *
	 * @param ImageSizeItemModel[]|Collection $imageSizeItems The image size items collection
	 *
	 * @return $this The picture object
	 */
	public function setImageSizeItems($imageSizeItems)
	{
		if ($imageSizeItems === null)
		{
			$imageSizeItems = array();
		}

		$this->imageSizeItems = $imageSizeItems;

		return $this;
	}

	/**
	 * Get the picture element definition array
	 *
	 * @return array The picture element definition
	 */
	public function getTemplateData()
	{
		$rootDir = System::getContainer()->getParameter('kernel.project_dir');
		$image = System::getContainer()->get('contao.image.image_factory')->create($rootDir . '/' . $this->image->getOriginalPath());

		$config = new PictureConfiguration();
		$config->setSize($this->getConfigurationItem($this->imageSize));

		$sizeItems = array();

		foreach ($this->imageSizeItems as $imageSizeItem)
		{
			$sizeItems[] = $this->getConfigurationItem($imageSizeItem);
		}

		$config->setSizeItems($sizeItems);

		$importantPart = $this->image->getImportantPart();

		$image->setImportantPart(
			new ImportantPart(
				new Point($importantPart['x'], $importantPart['y']),
				new Box($importantPart['width'], $importantPart['height'])
			)
		);

		$container = System::getContainer();
		$staticUrl = $container->get('contao.assets.files_context')->getStaticUrl();

		$picture = $container
			->get('contao.image.picture_generator')
			->generate(
				$image,
				$config,
				(new ResizeOptions())
					->setImagineOptions($container->getParameter('contao.image.imagine_options'))
					->setBypassCache($container->getParameter('contao.image.bypass_cache'))
			)
		;

		return array
		(
			'img' => $picture->getImg($rootDir, $staticUrl),
			'sources' => $picture->getSources($rootDir, $staticUrl),
		);
	}

	/**
	 * Get the config for one picture source element
	 *
	 * @param Model|object $imageSize The image size or image size item model
	 *
	 * @return PictureConfigurationItem
	 */
	protected function getConfigurationItem($imageSize)
	{
		$configItem = new PictureConfigurationItem();
		$resizeConfig = new ResizeConfiguration();

		$mode = $imageSize->resizeMode;

		if (substr_count($mode, '_') === 1)
		{
			$importantPart = $this->image->setImportantPart(null)->getImportantPart();

			$mode = explode('_', $mode);

			if ($mode[0] === 'left')
			{
				$importantPart['width'] = 1;
			}
			elseif ($mode[0] === 'right')
			{
				$importantPart['x'] = $importantPart['width'] - 1;
				$importantPart['width'] = 1;
			}

			if ($mode[1] === 'top')
			{
				$importantPart['height'] = 1;
			}
			elseif ($mode[1] === 'bottom')
			{
				$importantPart['y'] = $importantPart['height'] - 1;
				$importantPart['height'] = 1;
			}

			$this->image->setImportantPart($importantPart);

			$mode = ResizeConfiguration::MODE_CROP;
		}

		$resizeConfig
			->setWidth($imageSize->width)
			->setHeight($imageSize->height)
			->setZoomLevel($imageSize->zoom)
		;

		if ($mode)
		{
			$resizeConfig->setMode($mode);
		}

		$configItem->setResizeConfig($resizeConfig);

		if (isset($imageSize->sizes))
		{
			$configItem->setSizes($imageSize->sizes);
		}

		if (isset($imageSize->densities))
		{
			$configItem->setDensities($imageSize->densities);
		}

		if (isset($imageSize->media))
		{
			$configItem->setMedia($imageSize->media);
		}

		return $configItem;
	}
}

class_alias(Picture::class, 'Picture');


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Provide methods to handle radio buttons.
 *
 * @property boolean $mandatory
 * @property array   $options
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class RadioButton extends Widget
{

	/**
	 * Submit user input
	 * @var boolean
	 */
	protected $blnSubmitInput = true;

	/**
	 * Template
	 * @var string
	 */
	protected $strTemplate = 'be_widget_rdo';

	/**
	 * Add specific attributes
	 *
	 * @param string $strKey
	 * @param mixed  $varValue
	 */
	public function __set($strKey, $varValue)
	{
		switch ($strKey)
		{
			case 'mandatory':
				if ($varValue)
				{
					$this->arrAttributes['required'] = 'required';
				}
				else
				{
					unset($this->arrAttributes['required']);
				}
				parent::__set($strKey, $varValue);
				break;

			case 'options':
				$this->arrOptions = StringUtil::deserialize($varValue);
				break;

			default:
				parent::__set($strKey, $varValue);
				break;
		}
	}

	/**
	 * Check for a valid option (see #4383)
	 */
	public function validate()
	{
		$varValue = $this->getPost($this->strName);

		if (!empty($varValue) && !$this->isValidOption($varValue))
		{
			$this->addError($GLOBALS['TL_LANG']['ERR']['invalid']);
		}

		parent::validate();
	}

	/**
	 * Generate the widget and return it as string
	 *
	 * @return string
	 */
	public function generate()
	{
		$arrOptions = array();

		foreach ($this->arrOptions as $i=>$arrOption)
		{
			$arrOptions[] = sprintf('<input type="radio" name="%s" id="opt_%s" class="tl_radio" value="%s"%s%s onfocus="Backend.getScrollOffset()"> %s',
									 $this->strName,
									 $this->strId.'_'.$i,
									 StringUtil::specialchars($arrOption['value']),
									 $this->isChecked($arrOption),
									 $this->getAttributes(),
									 $this->strId.'_'.$i,
									 $arrOption['label']);
		}

		// Add a "no entries found" message if there are no options
		if (empty($arrOptions))
		{
			$arrOptions[]= '
'.$GLOBALS['TL_LANG']['MSC']['noResult'].'
';
		}

		return sprintf('<fieldset id="ctrl_%s" class="tl_radio_container%s"><legend>%s%s%s%s</legend>%s</fieldset>%s',
						$this->strId,
						(($this->strClass != '') ? ' ' . $this->strClass : ''),
						($this->mandatory ? ''.$GLOBALS['TL_LANG']['MSC']['mandatory'].' ' : ''),
						$this->strLabel,
						($this->mandatory ? '*' : ''),
						$this->xlabel,
						implode('
', $arrOptions),
						$this->wizard);
	}
}

class_alias(RadioButton::class, 'RadioButton');


<?php

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao;

/**
 * Provide methods to handle sections of a page layout.
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class SectionWizard extends Widget
{

	/**
	 * Submit user input
	 * @var boolean
	 */
	protected $blnSubmitInput = true;

	/**
	 * Template
	 * @var string
	 */
	protected $strTemplate = 'be_widget';

	/**
	 * Standardize the ID
	 *
	 * @param mixed $varInput
	 *
	 * @return mixed
	 */
	protected function validator($varInput)
	{
		$arrTitles = array();
		$arrIds = array();
		$arrSections = array();

		foreach ($varInput as $arrSection)
		{
			// Title and ID are required
			if ((!empty($arrSection['title']) && empty($arrSection['id'])) || (empty($arrSection['title']) && !empty($arrSection['id'])))
			{
				$this->addError($GLOBALS['TL_LANG']['ERR']['emptyTitleOrId']);
			}

			// Check for duplicate section titles
			if (\in_array($arrSection['title'], $arrTitles))
			{
				$this->addError(sprintf($GLOBALS['TL_LANG']['ERR']['duplicateSectionTitle'], $arrSection['title']));
			}

			$arrSection['id'] = StringUtil::standardize($arrSection['id'], true);

			// Add a suffix to reserved names (see #301)
			if (\in_array($arrSection['id'], array('top', 'wrapper', 'header', 'container', 'main', 'left', 'right', 'footer')))
			{
				$arrSection['id'] .= '-custom';
			}

			// Check for duplicate section IDs
			if (\in_array($arrSection['id'], $arrIds))
			{
				$this->addError(sprintf($GLOBALS['TL_LANG']['ERR']['duplicateSectionId'], $arrSection['id']));
			}

			$arrTitles[] = $arrSection['title'];
			$arrIds[] = $arrSection['id'];
			$arrSections[] = $arrSection;
		}

		return $arrSections;
	}

	/**
	 * Generate the widget and return it as string
	 *
	 * @return string
	 */
	public function generate()
	{
		$arrButtons = array('copy', 'delete', 'drag');

		// Make sure there is at least an empty array
		if (!\is_array($this->varValue) || !$this->varValue[0])
		{
			$this->varValue = array(array(''));
		}

		// Add the label and the return wizard
		$return = '
strId.'" class="tl_sectionwizard">

    '.$GLOBALS['TL_LANG']['MSC']['sw_title'].'
    '.$GLOBALS['TL_LANG']['MSC']['sw_id'].'
    '.$GLOBALS['TL_LANG']['MSC']['sw_template'].'
    '.$GLOBALS['TL_LANG']['MSC']['sw_position'].'

';

		// Add the input fields
		for ($i=0, $c=\count($this->varValue); $i<$c; $i++)
		{
			$return .= '

      <input type="text" name="'.$this->strId.'['.$i.'][title]" id="'.$this->strId.'_title_'.$i.'" class="tl_text" value="'.StringUtil::specialchars($this->varValue[$i]['title']).'">
      <input type="text" name="'.$this->strId.'['.$i.'][id]" id="'.$this->strId.'_id_'.$i.'" class="tl_text" value="'.StringUtil::specialchars($this->varValue[$i]['id']).'">	';

			$options = '';

			// Add the template
			foreach (Template::getTemplateGroup('block_section_') as $k=>$v)
			{
				$options .= '<option value="'.StringUtil::specialchars($k).'"'.static::optionSelected($k, $this->varValue[$i]['template']).'>'.$v.'</option>';
			}

			$return .= '
    <select name="'.$this->strId.'['.$i.'][template]" class="tl_select" onfocus="Backend.getScrollOffset()">'.$options.'</select>	';

			$options = '';

			// Add the positions
			foreach (array('top', 'before', 'main', 'after', 'bottom', 'manual') as $v)
			{
				$options .= '<option value="'.StringUtil::specialchars($v).'"'.static::optionSelected($v, $this->varValue[$i]['position']).'>'.$GLOBALS['TL_LANG']['SECTIONS'][$v].'</option>';
			}

			$return .= '
    <select name="'.$this->strId.'['.$i.'][position]" class="tl_select" onfocus="Backend.getScrollOffset()">'.$options.'</select>
    ';

			// Add the buttons
			foreach ($arrButtons as $button)
			{
				if ($button == 'drag')
				{
					$return .= ' <button type="button" class="drag-handle" title="' . StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['move']) . '" aria-hidden="true">' . Image::getHtml('drag.svg') . '</button>';
				}
				else
				{
					$return .= ' <button type="button" data-command="' . $button . '" title="' . StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['mw_'.$button]) . '">' . Image::getHtml($button.'.svg') . '</button>';
				}
			}

			$return .= '

';
		}

		return $return.'

  ';
	}
}

class_alias(SectionWizard::class, 'SectionWizard');