<?php

declare(strict_types=1);

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

namespace Contao\CoreBundle\Config\Loader;

use Symfony\Component\Config\Loader\Loader;

/**
 * Reads PHP files and returns the content without the opening and closing PHP tags.
 */
class PhpFileLoader extends Loader
{
    /**
     * {@inheritdoc}
     */
    public function load($file, $type = null): string
    {
        [$code, $namespace] = $this->parseFile((string) $file);

        $code = $this->stripLegacyCheck($code);

        if (false !== $namespace && 'namespaced' === $type) {
            $code = sprintf("\nnamespace %s {%s}\n", $namespace, $code);
        }

        return $code;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($resource, $type = null): bool
    {
        return 'php' === pathinfo((string) $resource, PATHINFO_EXTENSION);
    }

    /**
     * Parses a file and returns the code and namespace.
     *
     * @return string[]
     */
    private function parseFile(string $file): array
    {
        $code = '';
        $namespace = '';
        $buffer = false;
        $stream = new \PHP_Token_Stream($file);

        foreach ($stream as $token) {
            switch (true) {
                case $token instanceof \PHP_Token_OPEN_TAG:
                case $token instanceof \PHP_Token_CLOSE_TAG:
                    // remove
                    break;

                case false !== $buffer:
                    $buffer .= $token;

                    if (';' === (string) $token) {
                        $code .= $this->handleDeclare($buffer);
                        $buffer = false;
                    }
                    break;

                case $token instanceof \PHP_Token_NAMESPACE:
                    if ('{' === $token->getName()) {
                        $namespace = false;
                        $code .= $token;
                    } else {
                        $namespace = $token->getName();
                        $stream->seek($token->getEndTokenId());
                    }
                    break;

                case $token instanceof \PHP_Token_DECLARE:
                    $buffer = (string) $token;
                    break;

                default:
                    $code .= $token;
            }
        }

        return [$code, $namespace];
    }

    private function handleDeclare(string $code): string
    {
        $code = preg_replace('/(,\s*)?strict_types\s*=\s*1(\s*,)?/', '', $code);

        if (preg_match('/declare\(\s*\)/', $code)) {
            return '';
        }

        return str_replace(' ', '', $code);
    }

    private function stripLegacyCheck(string $code): string
    {
        $code = str_replace(
            [
                "if (!defined('TL_ROOT')) die('You cannot access this file directly!');",
                "if (!defined('TL_ROOT')) die('You can not access this file directly!');",
            ],
            '',
            $code
        );

        return "\n".trim($code)."\n";
    }
}


<?php

declare(strict_types=1);

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

namespace Contao\CoreBundle\Cors;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\DriverException;
use Nelmio\CorsBundle\Options\ProviderInterface;
use Symfony\Component\HttpFoundation\Request;

class WebsiteRootsConfigProvider implements ProviderInterface
{
    /**
     * @var Connection
     */
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions(Request $request): array
    {
        if (!$this->isCorsRequest($request) || !$this->canRunDbQuery()) {
            return [];
        }

        $stmt = $this->connection->prepare("
            SELECT EXISTS (
                SELECT
                    id
                FROM
                    tl_page
                WHERE
                    type = 'root' AND dns = :dns
            )
        ");

        $stmt->bindValue('dns', preg_replace('@^https?://@', '', $request->headers->get('origin')));
        $stmt->execute();

        if (!$stmt->fetchColumn()) {
            return [];
        }

        return [
            'allow_origin' => true,
            'allow_methods' => ['HEAD', 'GET'],
            'allow_headers' => ['x-requested-with'],
        ];
    }

    /**
     * Checks if the request has an Origin header.
     */
    private function isCorsRequest(Request $request): bool
    {
        return $request->headers->has('Origin')
            && $request->headers->get('Origin') !== $request->getSchemeAndHttpHost()
        ;
    }

    /**
     * Checks if the tl_page table exists.
     */
    private function canRunDbQuery(): bool
    {
        try {
            return $this->connection->getSchemaManager()->tablesExist(['tl_page']);
        } catch (DriverException $e) {
            return false;
        }
    }
}


<?php

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

namespace Contao;

use Contao\Image\ResizeConfiguration;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;

/**
 * Provide methods to handle input field "file tree".
 *
 * @property string  $path
 * @property string  $fieldType
 * @property string  $sort
 * @property boolean $files
 * @property boolean $filesOnly
 * @property string  $extensions
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class FileSelector extends Widget
{

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

	/**
	 * Path nodes
	 * @var array
	 */
	protected $arrNodes = array();

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

	/**
	 * Valid file types
	 * @var array
	 */
	protected $arrValidFileTypes = array();

	/**
	 * Load the database object
	 *
	 * @param array $arrAttributes
	 */
	public function __construct($arrAttributes=null)
	{
		$this->import(Database::class, 'Database');
		parent::__construct($arrAttributes);
	}

	/**
	 * Generate the widget and return it as string
	 *
	 * @return string
	 */
	public function generate()
	{
		$this->import(BackendUser::class, 'User');
		$this->convertValuesToPaths();

		if ($this->extensions != '')
		{
			$this->arrValidFileTypes = StringUtil::trimsplit(',', strtolower($this->extensions));
		}

		/** @var AttributeBagInterface $objSessionBag */
		$objSessionBag = System::getContainer()->get('session')->getBag('contao_backend');

		// Store the keyword
		if (Input::post('FORM_SUBMIT') == 'item_selector')
		{
			$strKeyword = ltrim(Input::postRaw('keyword'), '*');

			// Make sure the regular expression is valid
			if ($strKeyword != '')
			{
				try
				{
					$this->Database->prepare("SELECT * FROM tl_files WHERE name REGEXP ?")
								   ->limit(1)
								   ->execute($strKeyword);
				}
				catch (\Exception $e)
				{
					$strKeyword = '';
				}
			}

			$objSessionBag->set('file_selector_search', $strKeyword);
			$this->reload();
		}

		$tree = '';
		$for = $objSessionBag->get('file_selector_search');
		$arrFound = array();

		// Search for a specific file
		if ($for != '')
		{
			// Wrap in a try catch block in case the regular expression is invalid (see #7743)
			try
			{
				$strPattern = "CAST(name AS CHAR) REGEXP ?";

				if (substr(Config::get('dbCollation'), -3) == '_ci')
				{
					$strPattern = "LOWER(CAST(name AS CHAR)) REGEXP LOWER(?)";
				}

				$strType = '';

				if (strpos($for, 'type:file') !== false)
				{
					$strType = " AND type='file'";
					$for = trim(str_replace('type:file', '', $for));
				}

				if (strpos($for, 'type:folder') !== false)
				{
					$strType = " AND type='folder'";
					$for = trim(str_replace('type:folder', '', $for));
				}

				$objRoot = $this->Database->prepare("SELECT path, type, extension FROM tl_files WHERE $strPattern $strType")
										  ->execute($for);

				if ($objRoot->numRows < 1)
				{
					$GLOBALS['TL_DCA']['tl_files']['list']['sorting']['root'] = array('');
				}
				else
				{
					$arrPaths = array();

					// Respect existing limitations
					if ($this->path != '')
					{
						while ($objRoot->next())
						{
							if (strncmp($this->path . '/', $objRoot->path . '/', \strlen($this->path) + 1) === 0)
							{
								if ($objRoot->type == 'folder' || empty($this->arrValidFileTypes) || \in_array($objRoot->extension, $this->arrValidFileTypes))
								{
									$arrFound[] = $objRoot->path;
								}

								$arrPaths[] = ($objRoot->type == 'folder') ? $objRoot->path : \dirname($objRoot->path);
							}
						}
					}
					elseif ($this->User->isAdmin)
					{
						// Show all files to admins
						while ($objRoot->next())
						{
							if ($objRoot->type == 'folder' || empty($this->arrValidFileTypes) || \in_array($objRoot->extension, $this->arrValidFileTypes))
							{
								$arrFound[] = $objRoot->path;
							}

							$arrPaths[] = ($objRoot->type == 'folder') ? $objRoot->path : \dirname($objRoot->path);
						}
					}
					else
					{
						if (\is_array($this->User->filemounts))
						{
							while ($objRoot->next())
							{
								// Show only mounted folders to regular users
								foreach ($this->User->filemounts as $path)
								{
									if (strncmp($path . '/', $objRoot->path . '/', \strlen($path) + 1) === 0)
									{
										if ($objRoot->type == 'folder' || empty($this->arrValidFileTypes) || \in_array($objRoot->extension, $this->arrValidFileTypes))
										{
											$arrFound[] = $objRoot->path;
										}

										$arrPaths[] = ($objRoot->type == 'folder') ? $objRoot->path : \dirname($objRoot->path);
									}
								}
							}
						}
					}

					$GLOBALS['TL_DCA']['tl_files']['list']['sorting']['root'] = array_unique($arrPaths);
				}
			}
			catch (\Exception $e) {}
		}

		$strNode = $objSessionBag->get('tl_files_picker');

		// Unset the node if it is not within the path (see #5899)
		if ($strNode != '' && $this->path != '')
		{
			if (strncmp($strNode . '/', $this->path . '/', \strlen($this->path) + 1) !== 0)
			{
				$objSessionBag->remove('tl_files_picker');
			}
		}

		// Add the breadcrumb menu
		if (Input::get('do') != 'files')
		{
			Backend::addFilesBreadcrumb('tl_files_picker');
		}

		$rootDir = System::getContainer()->getParameter('kernel.project_dir');

		// Root nodes (breadcrumb menu)
		if (!empty($GLOBALS['TL_DCA']['tl_files']['list']['sorting']['root']))
		{
			$root = $GLOBALS['TL_DCA']['tl_files']['list']['sorting']['root'];

			// Allow only those roots that are within the custom path
			if ($this->path != '')
			{
				$root = array_intersect(preg_grep('/^' . preg_quote($this->path, '/') . '(?:$|\/)/', $root), $root);

				if (empty($root))
				{
					// Set all folders inside the custom path as root nodes
					$root = array_map(function ($el) { return $this->path . '/' . $el; }, scan($rootDir . '/' . $this->path));

					// Hide the breadcrumb
					$GLOBALS['TL_DCA']['tl_file']['list']['sorting']['breadcrumb'] = '';
				}
			}

			$nodes = $this->eliminateNestedPaths($root);

			foreach ($nodes as $node)
			{
				$tree .= $this->renderFiletree($rootDir . '/' . $node, 0, true, true, $arrFound);
			}
		}

		// Show a custom path (see #4926)
		elseif ($this->path != '')
		{
			$tree .= $this->renderFiletree($rootDir . '/' . $this->path, 0, false, $this->isProtectedPath($this->path), $arrFound);
		}

		// Start from root
		elseif ($this->User->isAdmin)
		{
			$tree .= $this->renderFiletree($rootDir . '/' . Config::get('uploadPath'), 0, false, true, $arrFound);
		}

		// Show mounted files to regular users
		else
		{
			$nodes = $this->eliminateNestedPaths($this->User->filemounts);

			foreach ($nodes as $node)
			{
				$tree .= $this->renderFiletree($rootDir . '/' . $node, 0, true, true, $arrFound);
			}
		}

		// Select all checkboxes
		if ($this->fieldType == 'checkbox')
		{
			$strReset = "\n" . '

strId . '" class="tl_change_selected">' . $GLOBALS['TL_LANG']['MSC']['selectAll'] . ' <input type="checkbox" id="check_all_' . $this->strId . '" class="tl_tree_checkbox" value="" onclick="Backend.toggleCheckboxGroup(this,\'' . $this->strName . '\')">

';
		}
		// Reset radio button selection
		else
		{
			$strReset = "\n" . '

strId . '" class="tl_change_selected">' . $GLOBALS['TL_LANG']['MSC']['resetSelected'] . ' <input type="radio" name="' . $this->strName . '" id="reset_' . $this->strName . '" class="tl_tree_radio" value="" onfocus="Backend.getScrollOffset()">

';
		}

		// Return the tree
		return '
strClass != '') ? ' ' . $this->strClass : '').'" id="'.$this->strId.'" data-callback="reloadFiletree">

'.Image::getHtml($GLOBALS['TL_DCA']['tl_files']['list']['sorting']['icon'] ?: 'filemounts.svg').' '.$GLOBALS['TL_LANG']['MOD']['files'][0].'

strId.'_parent">
'.$tree.$strReset.'

';
	}

	/**
	 * Generate a particular subpart of the file tree and return it as HTML string
	 *
	 * @param integer $strFolder
	 * @param string  $strField
	 * @param integer $level
	 * @param boolean $mount
	 *
	 * @return string
	 */
	public function generateAjax($strFolder, $strField, $level, $mount=false)
	{
		if (!Environment::get('isAjaxRequest'))
		{
			return '';
		}

		$this->strField = $strField;
		$this->loadDataContainer($this->strTable);

		// Load the current values
		switch ($GLOBALS['TL_DCA'][$this->strTable]['config']['dataContainer'])
		{
			case 'File':
				if (Config::get($this->strField) != '')
				{
					$this->varValue = Config::get($this->strField);
				}
				break;

			case 'Table':
				$this->import(Database::class, 'Database');

				if (!$this->Database->fieldExists($this->strField, $this->strTable))
				{
					break;
				}

				$objField = $this->Database->prepare("SELECT " . Database::quoteIdentifier($this->strField) . " FROM " . $this->strTable . " WHERE id=?")
										   ->limit(1)
										   ->execute($this->strId);

				if ($objField->numRows)
				{
					$this->varValue = StringUtil::deserialize($objField->{$this->strField});
				}
				break;
		}

		$this->convertValuesToPaths();

		if ($this->extensions != '')
		{
			$this->arrValidFileTypes = StringUtil::trimsplit(',', $this->extensions);
		}

		return $this->renderFiletree(System::getContainer()->getParameter('kernel.project_dir') . '/' . $strFolder, ($level * 20), $mount, $this->isProtectedPath($strFolder));
	}

	/**
	 * Recursively render the filetree
	 *
	 * @param string  $path
	 * @param integer $intMargin
	 * @param boolean $mount
	 * @param boolean $blnProtected
	 * @param array   $arrFound
	 *
	 * @return string
	 */
	protected function renderFiletree($path, $intMargin, $mount=false, $blnProtected=true, $arrFound=array())
	{
		// Invalid path
		if (!is_dir($path))
		{
			return '';
		}

		// Make sure that $this->varValue is an array (see #3369)
		if (!\is_array($this->varValue))
		{
			$this->varValue = array($this->varValue);
		}

		/** @var AttributeBagInterface $objSessionBag */
		$objSessionBag = System::getContainer()->get('session')->getBag('contao_backend');

		$session = $objSessionBag->all();

		$flag = substr($this->strField, 0, 2);
		$node = 'tree_' . $this->strTable . '_' . $this->strField;
		$xtnode = 'tree_' . $this->strTable . '_' . $this->strName;

		// Get session data and toggle nodes
		if (Input::get($flag.'tg'))
		{
			$session[$node][Input::get($flag.'tg')] = (isset($session[$node][Input::get($flag.'tg')]) && $session[$node][Input::get($flag.'tg')] == 1) ? 0 : 1;
			$objSessionBag->replace($session);
			$this->redirect(preg_replace('/(&(amp;)?|\?)'.$flag.'tg=[^& ]*/i', '', Environment::get('request')));
		}

		$return = '';
		$intSpacing = 20;
		$files = array();
		$folders = array();
		$level = ($intMargin / $intSpacing + 1);

		// Mount folder
		if ($mount)
		{
			$folders = array($path);
		}

		// Scan directory and sort the result
		else
		{
			foreach (scan($path) as $v)
			{
				if (strncmp($v, '.', 1) === 0)
				{
					continue;
				}

				if (is_dir($path . '/' . $v))
				{
					$folders[] = $path . '/' . $v;
				}
				else
				{
					$files[] = $path . '/' . $v;
				}
			}
		}

		natcasesort($folders);
		$folders = array_values($folders);

		natcasesort($files);
		$files = array_values($files);

		// Sort descending (see #4072)
		if ($this->sort == 'desc')
		{
			$folders = array_reverse($folders);
			$files = array_reverse($files);
		}

		$folderClass = ($this->files || $this->filesOnly) ? 'tl_folder' : 'tl_file';

		// Process folders
		for ($f=0, $c=\count($folders); $f<$c; $f++)
		{
			$content = scan($folders[$f]);
			$currentFolder = StringUtil::stripRootDir($folders[$f]);
			$countFiles = \count($content);

			// Check whether there are subfolders or files
			foreach ($content as $file)
			{
				if (strncmp($file, '.', 1) === 0)
				{
					--$countFiles;
				}
				elseif (!$this->files && !$this->filesOnly && is_file($folders[$f] . '/' . $file))
				{
					--$countFiles;
				}
				elseif (!empty($arrFound) && !\in_array($currentFolder . '/' . $file, $arrFound) && !preg_grep('/^' . preg_quote($currentFolder . '/' . $file, '/') . '\//', $arrFound))
				{
					--$countFiles;
				}
			}

			if (!empty($arrFound) && $countFiles < 1 && !\in_array($currentFolder, $arrFound))
			{
				continue;
			}

			$tid = md5($folders[$f]);
			$folderAttribute = 'style="margin-left:20px"';
			$session[$node][$tid] = is_numeric($session[$node][$tid]) ? $session[$node][$tid] : 0;
			$currentFolder = StringUtil::stripRootDir($folders[$f]);
			$blnIsOpen = (!empty($arrFound) || $session[$node][$tid] == 1 || \count(preg_grep('/^' . preg_quote($currentFolder, '/') . '\//', $this->varValue)) > 0);
			$return .= "\n    " . '

';

			// Add a toggle button if there are childs
			if ($countFiles > 0)
			{
				$folderAttribute = '';
				$img = $blnIsOpen ? 'folMinus.svg' : 'folPlus.svg';
				$alt = $blnIsOpen ? $GLOBALS['TL_LANG']['MSC']['collapseNode'] : $GLOBALS['TL_LANG']['MSC']['expandNode'];
				$return .= 'strField.'\',\''.$this->strName.'\','.$level.')">'.Image::getHtml($img, '', 'style="margin-right:2px"').'';
			}

			$protected = $blnProtected;

			// Check whether the folder is public
			if ($protected === true && \in_array('.public', $content))
			{
				$protected = false;
			}

			$folderImg = $protected ? 'folderCP.svg' : 'folderC.svg';
			$folderLabel = ($this->files || $this->filesOnly) ? ''.StringUtil::specialchars(basename($currentFolder)).'' : StringUtil::specialchars(basename($currentFolder));

			// Add the current folder
			$return .= Image::getHtml($folderImg, '', $folderAttribute).' urlEncode($currentFolder)) . '" title="'.StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['selectNode']).'">'.$folderLabel.'

';

			// Add a checkbox or radio button
			if (!$this->filesOnly)
			{
				switch ($this->fieldType)
				{
					case 'checkbox':
						$return .= '<input type="checkbox" name="'.$this->strName.'[]" id="'.$this->strName.'_'.md5($currentFolder).'" class="tl_tree_checkbox" value="'.StringUtil::specialchars($currentFolder).'" onfocus="Backend.getScrollOffset()"'.$this->optionChecked($currentFolder, $this->varValue).'>';
						break;

					default:
					case 'radio':
						$return .= '<input type="radio" name="'.$this->strName.'" id="'.$this->strName.'_'.md5($currentFolder).'" class="tl_tree_radio" value="'.StringUtil::specialchars($currentFolder).'" onfocus="Backend.getScrollOffset()"'.$this->optionChecked($currentFolder, $this->varValue).'>';
						break;
				}
			}

			$return .= '

';

			// Call the next node
			if ($blnIsOpen)
			{
				$return .= '

';
				$return .= $this->renderFiletree($folders[$f], ($intMargin + $intSpacing), false, $protected, $arrFound);
				$return .= '

';
			}
		}

		// Process files
		if ($this->files || $this->filesOnly)
		{
			for ($h=0, $c=\count($files); $h<$c; $h++)
			{
				$thumbnail = '';
				$currentFile = StringUtil::stripRootDir($files[$h]);
				$currentEncoded = $this->urlEncode($currentFile);

				$objFile = new File($currentFile);

				if (!empty($this->arrValidFileTypes) && !\in_array($objFile->extension, $this->arrValidFileTypes))
				{
					continue;
				}

				// Ignore files not matching the search criteria
				if (!empty($arrFound) && !\in_array($currentFile, $arrFound))
				{
					continue;
				}

				$return .= "\n    " . '

';
				$thumbnail .= ' ('.$this->getReadableSize($objFile->filesize);

				if ($objFile->width && $objFile->height)
				{
					$thumbnail .= ', '.$objFile->width.'x'.$objFile->height.' px';
				}

				$thumbnail .= ')';

				// Generate thumbnail
				if ($objFile->isImage && $objFile->viewHeight > 0 && Config::get('thumbnails') && ($objFile->isSvgImage || ($objFile->height <= Config::get('gdMaxImgHeight') && $objFile->width <= Config::get('gdMaxImgWidth'))))
				{
					$rootDir = System::getContainer()->getParameter('kernel.project_dir');
					$thumbnail .= '
' . Image::getHtml(System::getContainer()->get('contao.image.image_factory')->create($rootDir . '/' . rawurldecode($currentEncoded), array(100, 75, ResizeConfiguration::MODE_BOX))->getUrl($rootDir), '', 'style="margin:0 0 2px -18px"');
					$importantPart = System::getContainer()->get('contao.image.image_factory')->create($rootDir . '/' . rawurldecode($currentEncoded))->getImportantPart();

					if ($importantPart->getPosition()->getX() > 0 || $importantPart->getPosition()->getY() > 0 || $importantPart->getSize()->getWidth() < $objFile->width || $importantPart->getSize()->getHeight() < $objFile->height)
					{
						$thumbnail .= ' ' . Image::getHtml(System::getContainer()->get('contao.image.image_factory')->create($rootDir . '/' . rawurldecode($currentEncoded), (new ResizeConfiguration())->setWidth(80)->setHeight(60)->setMode(ResizeConfiguration::MODE_BOX)->setZoomLevel(100))->getUrl($rootDir), '', 'style="margin:0 0 2px 0;vertical-align:bottom"');
					}
				}

				$return .= Image::getHtml($objFile->icon, $objFile->mime).' '.StringUtil::convertEncoding(StringUtil::specialchars(basename($currentFile)), Config::get('characterSet')).$thumbnail.'

';

				// Add checkbox or radio button
				switch ($this->fieldType)
				{
					case 'checkbox':
						$return .= '<input type="checkbox" name="'.$this->strName.'[]" id="'.$this->strName.'_'.md5($currentFile).'" class="tl_tree_checkbox" value="'.StringUtil::specialchars($currentFile).'" onfocus="Backend.getScrollOffset()"'.$this->optionChecked($currentFile, $this->varValue).'>';
						break;

					default:
					case 'radio':
						$return .= '<input type="radio" name="'.$this->strName.'" id="'.$this->strName.'_'.md5($currentFile).'" class="tl_tree_radio" value="'.StringUtil::specialchars($currentFile).'" onfocus="Backend.getScrollOffset()"'.$this->optionChecked($currentFile, $this->varValue).'>';
						break;
				}

				$return .= '

';
			}
		}

		return $return;
	}

	/**
	 * Translate the file IDs to file paths
	 */
	protected function convertValuesToPaths()
	{
		if (empty($this->varValue))
		{
			return;
		}

		if (!\is_array($this->varValue))
		{
			$this->varValue = array($this->varValue);
		}
		elseif (empty($this->varValue[0]))
		{
			$this->varValue = array();
		}

		if (empty($this->varValue))
		{
			return;
		}

		// TinyMCE will pass the path instead of the ID
		if (strpos($this->varValue[0], Config::get('uploadPath') . '/') === 0)
		{
			return;
		}

		// Ignore the numeric IDs when in switch mode (TinyMCE)
		if (Input::get('switch'))
		{
			return;
		}

		// Return if the custom path is not within the upload path (see #8562)
		if ($this->path != '' && strpos($this->path, Config::get('uploadPath') . '/') !== 0)
		{
			return;
		}

		$objFiles = FilesModel::findMultipleByIds($this->varValue);

		if ($objFiles !== null)
		{
			$this->varValue = array_values($objFiles->fetchEach('path'));
		}
	}

	/**
	 * Check if a path is protected (see #287)
	 *
	 * @param string $path
	 *
	 * @return boolean
	 */
	protected function isProtectedPath($path)
	{
		$rootDir = System::getContainer()->getParameter('kernel.project_dir');

		do
		{
			if (file_exists($rootDir . '/' . $path . '/.public'))
			{
				return false;
			}

			$path = \dirname($path);
		}
		while ($path != '.');

		return true;
	}
}

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


<?php

declare(strict_types=1);

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

namespace Contao\CoreBundle\DependencyInjection\Compiler;

use Contao\CoreBundle\Util\PackageUtil;
use PackageVersions\Versions;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class AddAssetsPackagesPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container): void
    {
        if (!$container->hasDefinition('assets.packages')) {
            return;
        }

        $this->addBundles($container);
        $this->addComponents($container);
    }

    /**
     * Adds every bundle with a public folder as assets package.
     */
    private function addBundles(ContainerBuilder $container): void
    {
        $packages = $container->getDefinition('assets.packages');
        $context = new Reference('contao.assets.assets_context');

        if ($container->hasDefinition('assets._version_default')) {
            $version = new Reference('assets._version_default');
        } else {
            $version = new Reference('assets.empty_version_strategy');
        }

        $bundles = $container->getParameter('kernel.bundles');
        $meta = $container->getParameter('kernel.bundles_metadata');

        foreach ($bundles as $name => $class) {
            if (!is_dir($meta[$name]['path'].'/Resources/public')) {
                continue;
            }

            $packageVersion = $version;
            $packageName = $this->getBundlePackageName($name);
            $serviceId = 'assets._package_'.$packageName;
            $basePath = 'bundles/'.preg_replace('/bundle$/', '', strtolower($name));

            if (is_file($meta[$name]['path'].'/Resources/public/manifest.json')) {
                $def = new ChildDefinition('assets.json_manifest_version_strategy');
                $def->replaceArgument(0, $meta[$name]['path'].'/Resources/public/manifest.json');

                $container->setDefinition('assets._version_'.$packageName, $def);
                $packageVersion = new Reference('assets._version_'.$packageName);
            }

            $container->setDefinition($serviceId, $this->createPackageDefinition($basePath, $packageVersion, $context));
            $packages->addMethodCall('addPackage', [$packageName, new Reference($serviceId)]);
        }
    }

    /**
     * Adds the Contao components as assets packages.
     */
    private function addComponents(ContainerBuilder $container): void
    {
        $packages = $container->getDefinition('assets.packages');
        $context = new Reference('contao.assets.assets_context');

        foreach (Versions::VERSIONS as $name => $version) {
            if (0 !== strncmp('contao-components/', $name, 18)) {
                continue;
            }

            $serviceId = 'assets._package_'.$name;
            $basePath = 'assets/'.substr($name, 18);
            $version = $this->createVersionStrategy($container, $version, $name);

            $container->setDefinition($serviceId, $this->createPackageDefinition($basePath, $version, $context));
            $packages->addMethodCall('addPackage', [$name, new Reference($serviceId)]);
        }
    }

    private function createPackageDefinition(string $basePath, Reference $version, Reference $context): Definition
    {
        $package = new ChildDefinition('assets.path_package');
        $package
            ->setPublic(false)
            ->replaceArgument(0, $basePath)
            ->replaceArgument(1, $version)
            ->replaceArgument(2, $context)
        ;

        return $package;
    }

    private function createVersionStrategy(ContainerBuilder $container, string $version, string $name): Reference
    {
        $def = new ChildDefinition('assets.static_version_strategy');
        $def->replaceArgument(0, PackageUtil::parseVersion($version));
        $def->replaceArgument(1, '%%s?v=%%s');

        $container->setDefinition('assets._version_'.$name, $def);

        return new Reference('assets._version_'.$name);
    }

    /**
     * Returns a bundle package name emulating what a bundle extension would look like.
     */
    private function getBundlePackageName(string $className): string
    {
        if ('Bundle' === substr($className, -6)) {
            $className = substr($className, 0, -6);
        }

        return Container::underscore($className);
    }
}