<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Error;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

/**
 * Global error handler for TYPO3 Console
 */
class ErrorHandler
{
    /**
     * @var array
     */
    protected $exceptionalErrors = [];

    /**
     * Defines which error levels result should result in an exception thrown.
     *
     * @param array $exceptionalErrors An array of E_* error levels
     * @return void
     */
    public function setExceptionalErrors(array $exceptionalErrors)
    {
        $this->exceptionalErrors = $exceptionalErrors;
    }

    /**
     * Handles an error by converting it into an exception.
     *
     * If error reporting is disabled, either in the php.ini or temporarily through
     * the shut-up operator "@", no exception will be thrown.
     *
     * @param int $errorLevel The error level - one of the E_* constants
     * @param string $errorMessage The error message
     * @param string $errorFile Name of the file the error occurred in
     * @param int $errorLine Line number where the error occurred
     * @throws \TYPO3\CMS\Core\Error\Exception with the data passed to this method
     * @return void
     */
    public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine)
    {
        if (error_reporting() === 0) {
            return;
        }

        $errorLevels = [
            E_WARNING => 'Warning',
            E_NOTICE => 'Notice',
            E_USER_ERROR => 'User Error',
            E_USER_WARNING => 'User Warning',
            E_USER_NOTICE => 'User Notice',
            E_STRICT => 'Runtime Notice',
            E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
        ];

        if (in_array($errorLevel, (array)$this->exceptionalErrors, true)) {
            throw new \TYPO3\CMS\Core\Error\Exception($errorLevels[$errorLevel] . ': ' . $errorMessage . ' in ' . $errorFile . ' line ' . $errorLine, 1);
        }
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\TYPO3v87\Install;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Install\InstallStepResponse;
use Helhum\Typo3Console\Install\Upgrade\SilentConfigurationUpgrade;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Install\Controller\Action\ActionInterface;
use TYPO3\CMS\Install\Controller\Action\Step\StepInterface;
use TYPO3\CMS\Install\Controller\Exception\RedirectException;
use TYPO3\CMS\Install\Status\ErrorStatus;

/**
 * This class is responsible for properly creating install tool step actions
 * and executing them if needed
 */
class InstallStepActionExecutor
{
    /**
     * @var ObjectManager
     */
    private $objectManager;

    /**
     * @var SilentConfigurationUpgrade
     */
    private $silentConfigurationUpgrade;

    /**
     * @param ObjectManager $objectManager
     * @param SilentConfigurationUpgrade $silentConfigurationUpgrade
     */
    public function __construct(ObjectManager $objectManager, SilentConfigurationUpgrade $silentConfigurationUpgrade)
    {
        $this->objectManager = $objectManager;
        $this->silentConfigurationUpgrade = $silentConfigurationUpgrade;
    }

    /**
     * Executes the given action and returns their response messages
     *
     * @param string $actionName Name of the install step
     * @param array $arguments Arguments for the install step
     * @param bool $dryRun If true, do not execute the action, but only check if execution is necessary
     * @return InstallStepResponse
     */
    public function executeActionWithArguments($actionName, array $arguments = [], $dryRun = false)
    {
        return $this->executeAction($this->createActionWithNameAndArguments($actionName, $arguments), $dryRun);
    }

    /**
     * @param string $actionName
     * @param array $arguments
     * @return StepInterface|ActionInterface
     */
    private function createActionWithNameAndArguments($actionName, array $arguments = [])
    {
        $classPrefix = 'TYPO3\\CMS\\Install\\Controller\\Action\\Step\\';
        $className = $classPrefix . ucfirst($actionName);

        /** @var StepInterface|ActionInterface $action */
        $action = $this->objectManager->get($className);
        $action->setController('step');
        $action->setAction($actionName);
        $action->setPostValues(['values' => $arguments]);

        return $action;
    }

    /**
     * @param StepInterface $action
     * @param bool $dryRun
     * @throws \TYPO3\CMS\Install\Controller\Exception\RedirectException
     * @return InstallStepResponse
     */
    private function executeAction(StepInterface $action, $dryRun = false)
    {
        $messages = [];
        try {
            $needsExecution = $action->needsExecution();
        } catch (RedirectException $e) {
            return new InstallStepResponse(true, $messages, true);
        }
        if ($needsExecution && !$dryRun) {
            try {
                $messages = $action->execute();
            } catch (\Throwable $e) {
                $errorMessage = new ErrorStatus();
                $errorMessage->setMessage($e->getMessage());
                $messages = [
                    $errorMessage,
                ];
            }
            $this->silentConfigurationUpgrade->executeSilentConfigurationUpgradesIfNeeded();
            $needsExecution = false;
        }

        return new InstallStepResponse($needsExecution, $messages);
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Composer;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Composer\Script\Event;
use Composer\Semver\Constraint\EmptyConstraint;
use Helhum\Typo3Console\Composer\InstallerScript\PopulateCommandConfiguration;
use TYPO3\CMS\Composer\Plugin\Core\InstallerScripts\AutoloadConnector;
use TYPO3\CMS\Composer\Plugin\Core\InstallerScripts\WebDirectory;
use TYPO3\CMS\Composer\Plugin\Core\InstallerScriptsRegistration;
use TYPO3\CMS\Composer\Plugin\Core\ScriptDispatcher;

/**
 * Scripts executed on composer build time
 */
class InstallerScripts implements InstallerScriptsRegistration
{
    /**
     * Allows to register one or more script objects that implement this interface
     * This will be called in the Plugin right before the scripts are executed.
     *
     * @param Event $event
     * @param ScriptDispatcher $scriptDispatcher
     * @return void
     */
    public static function register(Event $event, ScriptDispatcher $scriptDispatcher)
    {
        $scriptDispatcher->addInstallerScript(new PopulateCommandConfiguration(), 70);
        if (!class_exists(\TYPO3\CMS\Core\Composer\InstallerScripts::class)
            && !class_exists(\Helhum\Typo3ComposerSetup\Composer\InstallerScripts::class)
            && $event->getComposer()->getRepositoryManager()->getLocalRepository()->findPackage('typo3/cms', new EmptyConstraint()) !== null
        ) {
            // @deprecated can be removed once TYPO3 8 support is removed
            $scriptDispatcher->addInstallerScript(new WebDirectory());
            $scriptDispatcher->addInstallerScript(new AutoloadConnector());
        }
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Property\TypeConverter;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException;
use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;

/**
 * Converter which transforms arrays to arrays.
 *
 * @api
 */
class ArrayConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter
{
    /**
     * @var string
     */
    const CONFIGURATION_STRING_DELIMITER = 'stringDelimiter';

    /**
     * @var string
     */
    const DEFAULT_STRING_DELIMITER = ',';

    /**
     * @var array<string>
     */
    protected $sourceTypes = ['array', 'string'];

    /**
     * @var string
     */
    protected $targetType = 'array';

    /**
     * @var int
     */
    protected $priority = 15;

    /**
     * Convert from $source to $targetType, a noop if the source is an array.
     * If it is a string it will be exploded by the configured string delimiter.
     *
     * @param string|array $source
     * @param string $targetType
     * @param array $convertedChildProperties
     * @param PropertyMappingConfigurationInterface $configuration
     * @throws InvalidPropertyMappingConfigurationException
     * @return array
     */
    public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null): array
    {
        if (is_string($source)) {
            if ($source === '') {
                return [];
            }

            return array_filter(array_map('trim', explode($this->getConfiguredStringDelimiter($configuration), $source)));
        }

        return $source;
    }

    /**
     * @param PropertyMappingConfigurationInterface $configuration
     * @throws InvalidPropertyMappingConfigurationException
     * @return string
     */
    private function getConfiguredStringDelimiter(PropertyMappingConfigurationInterface $configuration = null): string
    {
        if ($configuration === null) {
            return self::DEFAULT_STRING_DELIMITER;
        }
        $stringDelimiter = $configuration->getConfigurationValue(self::class, self::CONFIGURATION_STRING_DELIMITER);
        if ($stringDelimiter === null) {
            return self::DEFAULT_STRING_DELIMITER;
        }
        if (!is_string($stringDelimiter)) {
            throw new InvalidPropertyMappingConfigurationException('CONFIGURATION_STRING_DELIMITER must be of type string, "' . (is_object($stringDelimiter) ? get_class($stringDelimiter) : gettype($stringDelimiter)) . '" given', 1368433339);
        }

        return $stringDelimiter;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\TYPO3v87\Install\Upgrade;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Symfony\Component\Console\Exception\RuntimeException;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Install\Controller\Exception\RedirectException;
use TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService;

/**
 * Used to migrate deprecated LocalConfiguration.php values to new values
 * It is a wrapper around the TYPO3 class to properly handle redirect exceptions
 */
class SilentConfigurationUpgrade
{
    /**
     * @var ConfigurationManager
     */
    private $configurationManager;

    /**
     * @var ObjectManagerInterface
     */
    private $objectManager;

    public function __construct(ConfigurationManager $configurationManager = null, ObjectManagerInterface $objectManager = null)
    {
        $this->configurationManager = $configurationManager ?: GeneralUtility::makeInstance(ConfigurationManager::class);
        $this->objectManager = $objectManager ?: GeneralUtility::makeInstance(ObjectManager::class);
    }

    /**
     * Call silent upgrade class, redirect to self if configuration was changed.
     *
     * @throws \UnexpectedValueException
     * @throws RuntimeException
     */
    public function executeSilentConfigurationUpgradesIfNeeded()
    {
        if (!file_exists($this->configurationManager->getLocalConfigurationFileLocation())) {
            return;
        }
        $upgradeService = $this->objectManager->get(SilentConfigurationUpgradeService::class);
        $count = 0;
        do {
            try {
                $count++;
                $upgradeService->execute();
                $redirect = false;
            } catch (RedirectException $e) {
                $redirect = true;
                $this->configurationManager->exportConfiguration();
                if ($count > 20) {
                    throw new RuntimeException('Too many loops when silently upgrading configuration', 1493897404, $e);
                }
            }
        } while ($redirect === true);
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Database\Schema;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */
use Helhum\Typo3Console\Mvc\Cli\ConsoleOutput;

/**
 * List of database schema update types
 */
class SchemaUpdateResultRenderer
{
    /**
     * Mapping of schema update types to human-readable labels
     *
     * @var array
     */
    private static $schemaUpdateTypeLabels = [
        SchemaUpdateType::FIELD_ADD => 'Add fields',
        SchemaUpdateType::FIELD_CHANGE => 'Change fields',
        SchemaUpdateType::FIELD_PREFIX => 'Prefix fields',
        SchemaUpdateType::FIELD_DROP => 'Drop fields',
        SchemaUpdateType::TABLE_ADD => 'Add tables',
        SchemaUpdateType::TABLE_CHANGE => 'Change tables',
        SchemaUpdateType::TABLE_PREFIX => 'Prefix tables',
        SchemaUpdateType::TABLE_DROP => 'Drop tables',
    ];

    /**
     * Renders a table for a schema update result
     *
     * @param SchemaUpdateResult $result Result of the schema update
     * @param ConsoleOutput $output
     * @param bool $includeStatements
     * @param int $maxStatementLength
     */
    public function render(SchemaUpdateResult $result, ConsoleOutput $output, $includeStatements = false, $maxStatementLength = 60)
    {
        $tableRows = [];

        foreach ($result->getPerformedUpdates() as $type => $performedUpdates) {
            $row = [self::$schemaUpdateTypeLabels[(string)$type], count($performedUpdates)];
            if ($includeStatements) {
                $row = [self::$schemaUpdateTypeLabels[(string)$type], implode(chr(10) . chr(10), $this->getTruncatedQueries($performedUpdates, $maxStatementLength))];
            }
            $tableRows[] = $row;
        }
        $tableHeader = ['Type', 'Updates'];
        if ($includeStatements) {
            $tableHeader = ['Type', 'SQL Statements'];
        }
        if (!empty($tableRows)) {
            $output->outputTable($tableRows, $tableHeader);
        }
    }

    /**
     * Renders a table for a schema update result
     *
     * @param SchemaUpdateResult $result Result of the schema update
     * @param ConsoleOutput $output
     * @param bool $includeStatements
     * @param int $maxStatementLength
     */
    public function renderErrors(SchemaUpdateResult $result, ConsoleOutput $output, $includeStatements = false, $maxStatementLength = 90)
    {
        $tableRows = [];
        $messageLength = $includeStatements ? (int)($maxStatementLength * .3) : $maxStatementLength;
        $statementLength = (int)($maxStatementLength * 0.6);
        foreach ($result->getErrors() as $type => $errors) {
            $typeLabel = self::$schemaUpdateTypeLabels[(string)$type];
            foreach ($errors as $error) {
                $row = [$typeLabel, implode(PHP_EOL, $this->getTruncatedQueries([$error['message']], $messageLength))];
                if ($includeStatements) {
                    $row = [$typeLabel, implode(PHP_EOL, $this->getTruncatedQueries([$error['statement']], $statementLength)), implode(PHP_EOL, $this->getTruncatedQueries([$error['message']], $messageLength))];
                }
                $tableRows[] = $row;
                $tableRows[] = $includeStatements ? ['', '', ''] : ['', ''];
                $typeLabel = '';
            }
        }
        $tableHeader = ['Type', 'Message'];
        if ($includeStatements) {
            $tableHeader = ['Type', 'SQL Statement', 'Message'];
        }
        $output->outputTable($tableRows, $tableHeader);
    }

    /**
     * Truncate (wrap) query strings at a certain number of characters
     *
     * @param array $queries
     * @param int $truncateAt
     * @return array
     */
    protected function getTruncatedQueries(array $queries, int $truncateAt): array
    {
        foreach ($queries as &$query) {
            $truncatedLines = [];
            foreach (explode(chr(10), $query) as $line) {
                $truncatedLines[] = wordwrap($line, $truncateAt, chr(10), true);
            }
            $query = implode(chr(10), $truncatedLines);
        }

        return $queries;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Mvc\Cli;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Mvc\Cli\Symfony\Output\TrackableOutput;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\Input;
use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

/**
 * A wrapper for Symfony ConsoleOutput and related helpers
 */
class ConsoleOutput
{
    /**
     * @var ArgvInput
     */
    protected $input;

    /**
     * @var TrackableOutput
     */
    protected $output;

    /**
     * @var QuestionHelper
     */
    protected $questionHelper;

    /**
     * @var ProgressBar
     */
    protected $progressBar;

    /**
     * @var Table
     */
    protected $table;

    /**
     * @var Terminal
     */
    private $terminal;

    /**
     * Creates and initializes the Symfony I/O instances
     *
     * @param Output|null $output
     * @param Input|null $input
     */
    public function __construct(Output $output = null, Input $input = null)
    {
        $this->output = new TrackableOutput($output ?: new SymfonyConsoleOutput());
        $this->output->getFormatter()->setStyle('b', new OutputFormatterStyle(null, null, ['bold']));
        $this->output->getFormatter()->setStyle('i', new OutputFormatterStyle('black', 'white'));
        $this->output->getFormatter()->setStyle('u', new OutputFormatterStyle(null, null, ['underscore']));
        $this->output->getFormatter()->setStyle('em', new OutputFormatterStyle(null, null, ['reverse']));
        $this->output->getFormatter()->setStyle('strike', new OutputFormatterStyle(null, null, ['conceal']));
        $this->output->getFormatter()->setStyle('success', new OutputFormatterStyle('green'));
        $this->output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
        $this->output->getFormatter()->setStyle('ins', new OutputFormatterStyle('green'));
        $this->output->getFormatter()->setStyle('del', new OutputFormatterStyle('red'));
        $this->output->getFormatter()->setStyle('code', new OutputFormatterStyle(null, null, ['bold']));
        $this->input = $input;
        $this->terminal = new Terminal();
    }

    /**
     * @return SymfonyConsoleOutput|TrackableOutput
     */
    public function getSymfonyConsoleOutput()
    {
        return $this->output;
    }

    /**
     * @return Input
     */
    public function getSymfonyConsoleInput(): Input
    {
        return $this->getInput();
    }

    /**
     * Returns the desired maximum line length for console output.
     *
     * @return int
     */
    public function getMaximumLineLength()
    {
        return $this->terminal->getWidth() - 2;
    }

    /**
     * Outputs specified text to the console window
     * You can specify arguments that will be passed to the text via sprintf
     * @see http://www.php.net/sprintf
     *
     * @param string $text Text to output
     * @param array $arguments Optional arguments to use for sprintf
     * @return void
     */
    public function output($text, array $arguments = [])
    {
        if ($arguments !== []) {
            $text = vsprintf($text, $arguments);
        }
        if (getenv('TYPO3_CONSOLE_SUB_PROCESS')) {
            $this->output->write($text, false, OutputInterface::OUTPUT_RAW);
        } else {
            $this->output->write($text);
        }
    }

    /**
     * Outputs specified text to the console window and appends a line break
     *
     * @param string $text Text to output
     * @param array $arguments Optional arguments to use for sprintf
     * @return void
     * @see output()
     * @see outputLines()
     */
    public function outputLine($text = '', array $arguments = [])
    {
        $this->output($text . PHP_EOL, $arguments);
    }

    /**
     * Formats the given text to fit into the maximum line length and outputs it to the
     * console window
     *
     * @param string $text Text to output
     * @param array $arguments Optional arguments to use for sprintf
     * @param int $leftPadding The number of spaces to use for indentation
     * @return void
     * @see outputLine()
     */
    public function outputFormatted($text = '', array $arguments = [], $leftPadding = 0)
    {
        $lines = explode(PHP_EOL, $text);
        foreach ($lines as $line) {
            $formattedText = str_repeat(' ', $leftPadding) . wordwrap($line, $this->getMaximumLineLength() - $leftPadding, PHP_EOL . str_repeat(' ', $leftPadding), true);
            $this->outputLine($formattedText, $arguments);
        }
    }

    /**
     * Renders a table like output of the given $rows
     *
     * @param array $rows
     * @param array $headers
     */
    public function outputTable($rows, $headers = null)
    {
        $table = $this->getTable();
        if ($headers !== null) {
            $table->setHeaders($headers);
        }
        $table->setRows($rows);
        $table->render();
    }

    /**
     * Asks the user to select a value
     *
     * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
     * @param array $choices List of choices to pick from
     * @param bool $default The default answer if the user enters nothing
     * @param bool $multiSelect If true the result will be an array with the selected options. Multiple options can be given separated by commas
     * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite)
     * @throws \Symfony\Component\Console\Exception\RuntimeException
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     * @return int|string|array The selected value or values (the key of the choices array)
     */
    public function select($question, $choices, $default = null, $multiSelect = false, $attempts = false)
    {
        $question = (new ChoiceQuestion($question, $choices, $default))
            ->setMultiselect($multiSelect)
            ->setMaxAttempts($attempts)
            ->setErrorMessage('Value "%s" is invalid');

        return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
    }

    /**
     * Asks a question to the user
     *
     * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
     * @param string $default The default answer if none is given by the user
     * @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     * @throws \Symfony\Component\Console\Exception\LogicException
     * @throws \Symfony\Component\Console\Exception\RuntimeException
     * @return string The user answer
     */
    public function ask($question, $default = null, array $autocomplete = null)
    {
        $question = (new Question($question, $default))
            ->setAutocompleterValues($autocomplete);

        return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
    }

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
     * @param bool $default The default answer if the user enters nothing
     * @return bool true if the user has confirmed, false otherwise
     */
    public function askConfirmation($question, $default = true)
    {
        $question = new ConfirmationQuestion($question, $default);

        return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
    }

    /**
     * Asks a question to the user, the response is hidden
     *
     * @param string|array $question The question. If an array each array item is turned into one line of a multi-line question
     * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
     * @throws \Symfony\Component\Console\Exception\LogicException
     * @throws \Symfony\Component\Console\Exception\RuntimeException
     * @return string The answer
     */
    public function askHiddenResponse($question, $fallback = true)
    {
        $question = (new Question($question))
            ->setHidden(true)
            ->setHiddenFallback($fallback);

        return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
    }

    /**
     * Asks for a value and validates the response
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
     * @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
     * @param int|bool $attempts Max number of times to ask before giving up (false by default, which means infinite)
     * @param string $default The default answer if none is given by the user
     * @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
     * @throws \Symfony\Component\Console\Exception\RuntimeException
     * @throws \Symfony\Component\Console\Exception\LogicException
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     * @return mixed
     */
    public function askAndValidate($question, $validator, $attempts = false, $default = null, array $autocomplete = null)
    {
        $question = (new Question($question, $default))
            ->setValidator($validator)
            ->setMaxAttempts($attempts)
            ->setAutocompleterValues($autocomplete);

        return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
    }

    /**
     * Asks for a value, hide and validates the response
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
     * @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
     * @param int|bool $attempts Max number of times to ask before giving up (false by default, which means infinite)
     * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     * @throws \Symfony\Component\Console\Exception\LogicException
     * @throws \Symfony\Component\Console\Exception\RuntimeException
     * @return string The response
     */
    public function askHiddenResponseAndValidate($question, $validator, $attempts = false, $fallback = true)
    {
        $question = (new Question($question))
            ->setValidator($validator)
            ->setMaxAttempts($attempts)
            ->setHidden(true)
            ->setHiddenFallback($fallback);

        return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
    }

    /**
     * Starts the progress output
     *
     * @param int $max Maximum steps. If NULL an indeterminate progress bar is rendered
     * @return void
     */
    public function progressStart($max = null)
    {
        $this->getProgressBar()->start($max);
    }

    /**
     * Advances the progress output X steps
     *
     * @param int $step Number of steps to advance
     * @throws \LogicException
     * @return void
     */
    public function progressAdvance($step = 1)
    {
        $this->getProgressBar()->advance($step);
    }

    /**
     * Sets the current progress
     *
     * @param int $current The current progress
     * @throws \LogicException
     * @return void
     */
    public function progressSet($current)
    {
        $this->getProgressBar()->setProgress($current);
    }

    /**
     * Finishes the progress output
     *
     * @return void
     */
    public function progressFinish()
    {
        $this->getProgressBar()->finish();
    }

    /**
     * @throws RuntimeException
     * @return ArgvInput
     */
    protected function getInput()
    {
        if ($this->input === null) {
            if (!isset($_SERVER['argv'])) {
                throw new RuntimeException('Cannot initialize ArgvInput object without CLI context.', 1456914444);
            }
            $this->input = new ArgvInput();
        }

        return $this->input;
    }

    /**
     * Returns or initializes the symfony/console QuestionHelper
     *
     * @return QuestionHelper
     */
    protected function getQuestionHelper()
    {
        if ($this->questionHelper === null) {
            $this->questionHelper = new QuestionHelper();
            $helperSet = new HelperSet([new FormatterHelper()]);
            $this->questionHelper->setHelperSet($helperSet);
        }

        return $this->questionHelper;
    }

    /**
     * Returns or initializes the symfony/console ProgressBar
     *
     * @return ProgressBar
     */
    protected function getProgressBar()
    {
        if ($this->progressBar === null) {
            $this->progressBar = new ProgressBar($this->output);
        }

        return $this->progressBar;
    }

    /**
     * Returns or initializes the symfony/console Table
     *
     * @return Table
     */
    protected function getTable()
    {
        if ($this->table === null) {
            $this->table = new Table($this->output);
        }

        return $this->table;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Service\Persistence;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Service\Delegation\ReferenceIndexIntegrityDelegateInterface;
use TYPO3\CMS\Core\Database\ReferenceIndex;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class PersistenceIntegrityService
{
    /**
     * @var PersistenceContextInterface
     */
    private $persistenceContext;

    public function __construct(PersistenceContextInterface $persistenceContext)
    {
        $this->persistenceContext = $persistenceContext;
    }

    /**
     * Updating Reference Index
     *
     * @param ReferenceIndexIntegrityDelegateInterface|null $delegate
     * @return array Tuple ($errorCount, $recordCount, $processedTables)
     */
    public function updateReferenceIndex(ReferenceIndexIntegrityDelegateInterface $delegate = null)
    {
        return $this->checkOrUpdateReferenceIndex(false, $delegate);
    }

    /**
     * Checking Reference Index
     *
     * @param ReferenceIndexIntegrityDelegateInterface|null $delegate
     * @return array Tuple ($errorCount, $recordCount, $processedTables)
     */
    public function checkReferenceIndex(ReferenceIndexIntegrityDelegateInterface $delegate = null)
    {
        return $this->checkOrUpdateReferenceIndex(true, $delegate);
    }

    /**
     * Updating or checking Reference Index
     *
     * @param bool $dryRun
     * @param ReferenceIndexIntegrityDelegateInterface|null $delegate
     * @return array Tuple ($errorCount, $recordCount, $processedTables)
     */
    protected function checkOrUpdateReferenceIndex($dryRun, ReferenceIndexIntegrityDelegateInterface $delegate = null)
    {
        $processedTables = [];
        $errorCount = 0;
        $recordCount = 0;

        $this->callDelegateForEvent($delegate, 'willStartOperation', [$this->persistenceContext->countAllRecordsOfAllTables()]);

        // Traverse all tables:
        foreach ($this->persistenceContext->getPersistenceConfiguration() as $tableName => $_) {
            // Traverse all records in tables, including deleted records:
            try {
                $records = $this->persistenceContext->getAllRecordsOfTable($tableName);
            } catch (TableDoesNotExistException $e) {
                $this->delegateLog($delegate, 'warning', $e->getMessage());
                continue;
            }
            if (!is_array($records) && !$records instanceof \Traversable) {
                $this->delegateLog($delegate, 'error', 'Table "%s" exists in $TCA but fetching records from database failed. Check the Database Analyzer in Install Tool for missing fields.', [$tableName]);
                continue;
            }
            $processedTables[] = $tableName;
            foreach ($records as $record) {
                $this->callDelegateForEvent($delegate, 'willUpdateRecord', [$tableName, $record]);

                /** @var $refIndexObj ReferenceIndex */
                $refIndexObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ReferenceIndex::class);
                if (isset($record['t3ver_wsid'])) {
                    $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
                }
                $result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $dryRun);
                $recordCount++;
                if ($result['addedNodes'] || $result['deletedNodes']) {
                    $errorMessage = 'Record ' . $tableName . ':' . $record['uid'];
                    if ($dryRun) {
                        $errorMessage .= ' has ' . $result['addedNodes'] . ' missing indexes and ' . $result['deletedNodes'] . ' stale indexes.';
                    } else {
                        $errorMessage .= ' ' . $result['addedNodes'] . ' indexes were added and ' . $result['deletedNodes'] . ' stale indexes were removed.';
                    }
                    $errorCount++;
                    $this->delegateLog($delegate, 'notice', $errorMessage);
                }
            }
            try {
                // Searching lost indexes for this table:
                $lostIndexCount = $this->persistenceContext->countLostIndexesOfRecordsInTable($tableName);
                if ($lostIndexCount > 0) {
                    $errorMessage = 'Table ' . $tableName . ' has ' . $lostIndexCount;
                    if ($dryRun) {
                        $errorMessage .= ' which need to be deleted.';
                    } else {
                        $errorMessage .= ' which have been deleted.';
                    }
                    $errorCount++;
                    $this->delegateLog($delegate, 'notice', $errorMessage);
                    if (!$dryRun) {
                        $this->persistenceContext->deleteLostIndexesOfRecordsInTable($tableName);
                    }
                }
            } catch (TableDoesNotExistException $e) {
                $this->delegateLog($delegate, 'warning', $e->getMessage());
                continue;
            }
        }

        // Searching lost indexes for non-existing tables:
        $lostTablesCount = $this->persistenceContext->countLostTables($processedTables);
        if ($lostTablesCount > 0) {
            $errorCount++;
            $this->delegateLog($delegate, 'notice', 'Found ' . $lostTablesCount . ' indexes for not existing tables.');
            if (!$dryRun) {
                $this->persistenceContext->deleteLostTables($processedTables);
                $this->delegateLog($delegate, 'info', 'Removed indexes for not existing tables.');
            }
        }

        $this->callDelegateForEvent($delegate, 'operationHasEnded');

        return [$errorCount, $recordCount, $processedTables];
    }

    /**
     * @param ReferenceIndexIntegrityDelegateInterface|null $delegate
     * @param string $eventName
     * @param array $arguments
     */
    protected function callDelegateForEvent($delegate, $eventName, $arguments = [])
    {
        if ($delegate === null) {
            return;
        }
        $delegate->$eventName(...$arguments);
    }

    /**
     * @param ReferenceIndexIntegrityDelegateInterface|null $delegate
     * @param string $severity
     * @param string $message
     * @param array $data
     */
    protected function delegateLog($delegate, $severity, $message, $data = [])
    {
        if ($delegate === null) {
            return;
        }
        $delegate->getLogger()->{$severity}($message, $data);
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Mvc\Cli\Symfony\Command;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Mvc\Cli\Symfony\Application;
use Helhum\Typo3Console\Mvc\Cli\Symfony\Descriptor\TextDescriptor;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Terminal;

class ListCommand extends \Symfony\Component\Console\Command\ListCommand
{
    protected function configure()
    {
        parent::configure();
        $this->amendDefinition($this->getDefinition());
    }

    public function getNativeDefinition()
    {
        return $this->amendDefinition(parent::getNativeDefinition());
    }

    /**
     * Subclass Symfony list command to be able to register our own text descriptor
     *
     * @param InputInterface $input
     * @param OutputInterface $output
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     * @return null|int null or 0 if everything went fine, or an error code
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = new DescriptorHelper();
        $helper->register('txt', new TextDescriptor());
        $helper->describe($output, $this->getApplication(), [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'show_unavailable' => $input->getOption('all'),
            'namespace' => $input->getArgument('namespace'),
            'screen_width' => (new Terminal())->getWidth() - 4,
        ]);
        $application = $this->getApplication();
        if (!$application instanceof Application) {
            return 0;
        }
        if (!$input->getArgument('namespace') && !$application->isFullyCapable() && !$input->getOption('all')) {
            $outputHelper = new SymfonyStyle($input, $output);
            $messages = [
                '',
                sprintf(
                    '<comment>TYPO3 %s.</comment>',
                    $application->hasErrors() ? 'has errors' : 'is not fully set up'
                ),
                '<comment>Command list is reduced to only show low level commands.</comment>',
                sprintf(
                    '<comment>Not listed commands will not work until %s.</comment>',
                    $application->hasErrors() ? 'the errors are fixed' : 'TYPO3 is set up'
                ),
                sprintf(
                    '<comment>Run "%s --all" to list all commands.</comment>',
                    $_SERVER['PHP_SELF']
                ),
            ];

            $outputHelper->getErrorStyle()->writeln($messages);
        }

        return null;
    }

    private function amendDefinition(InputDefinition $definition): InputDefinition
    {
        $definition->addOption(new InputOption(
            'all',
            '-a',
            InputOption::VALUE_NONE,
            'Show all commands, even the ones not available'
        ));

        return $definition;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Log\Writer;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Log\LogLevel;
use TYPO3\CMS\Core\Log\Writer\AbstractWriter;

/**
 * Class ConsoleWriter
 */
class ConsoleWriter extends AbstractWriter
{
    /**
     * @var OutputInterface
     */
    protected $output;

    /**
     * @var string
     */
    protected $messageWrap = '|';

    protected $severityTagMapping = [
        LogLevel::EMERGENCY => '<error>|</error>',
        LogLevel::ALERT => '<error>|</error>',
        LogLevel::CRITICAL => '<error>|</error>',
        LogLevel::ERROR => '<fg=red>|</fg=red>',
        LogLevel::WARNING => '<fg=yellow;options=bold>|</fg=yellow;options=bold>',
        LogLevel::NOTICE => '<fg=yellow>|</fg=yellow>',
        LogLevel::INFO => '<info>|</info>',
        LogLevel::DEBUG => '|',
    ];

    /**
     * @param OutputInterface $output
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * @param string $messageWrap
     */
    public function setMessageWrap($messageWrap)
    {
        $this->messageWrap = $messageWrap;
    }

    /**
     * Writes the log record
     *
     * @param \TYPO3\CMS\Core\Log\LogRecord $record Log record
     * @return \TYPO3\CMS\Core\Log\Writer\WriterInterface $this
     */
    public function writeLog(\TYPO3\CMS\Core\Log\LogRecord $record)
    {
        $this->output->write(
            $this->wrapMessage(vsprintf($record->getMessage(), $record->getData()), $record->getLevel()),
            true
        );

        return $this;
    }

    protected function wrapMessage($message, $level)
    {
        list($tagStart, $tagEnd) = explode('|', $this->severityTagMapping[$level]);
        list($wrapStart, $wrapEnd) = explode('|', $this->messageWrap);

        return $tagStart . $wrapStart . $message . $wrapEnd . $tagEnd;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Database\Schema;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

/**
 * Represents a database schema update result
 */
class SchemaUpdateResult
{
    /**
     * @var array
     */
    protected $performedUpdates = [];

    /**
     * @var array
     */
    protected $errors = [];

    /**
     * Returns the list of performed updates, grouped by schema update type
     *
     * @return array
     */
    public function getPerformedUpdates()
    {
        return $this->performedUpdates;
    }

    /**
     * Returns the list of performed update types including the count
     *
     * @return array
     */
    public function getPerformedUpdateTypes()
    {
        $typesCount = [];
        foreach ($this->performedUpdates as $type => $performedUpdates) {
            $typesCount[$type] = count($performedUpdates);
        }

        return $typesCount;
    }

    /**
     * Returns true if updates were performed, false otherwise
     *
     * @return bool
     */
    public function hasPerformedUpdates()
    {
        return count($this->performedUpdates) > 0;
    }

    /**
     * Adds to the number of updates performed for a schema update type
     *
     * @param SchemaUpdateType $schemaUpdateType Schema update type
     * @param array $updates Updates performed
     */
    public function addPerformedUpdates(SchemaUpdateType $schemaUpdateType, array $updates)
    {
        $this->performedUpdates[(string)$schemaUpdateType] = array_merge((array)$this->performedUpdates[(string)$schemaUpdateType], $updates);
    }

    /**
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Adds to the list of errors occurred for a schema update type
     *
     * @param SchemaUpdateType $schemaUpdateType Schema update type
     * @param array $errors List of error messages
     * @param array $statements SQL Statements that were executed
     */
    public function addErrors(SchemaUpdateType $schemaUpdateType, array $errors, array $statements = [])
    {
        $collectedErrors = [];
        foreach ($errors as $id => $error) {
            $collectedErrors[] = [
                'message' => $error,
                'statement' => $statements[$id],
            ];
        }
        $this->errors[(string)$schemaUpdateType] = array_merge((array)$this->errors[(string)$schemaUpdateType], $collectedErrors);
    }

    /**
     * Returns true if errors did occur during schema update, false otherwise
     *
     * @return bool
     */
    public function hasErrors()
    {
        return count($this->errors) > 0;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Command;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Mvc\Controller\CommandController;
use Helhum\Typo3Console\Service\Configuration\ConfigurationService;
use Helhum\Typo3Console\Service\Configuration\ConsoleRenderer\ConsoleRenderer;
use TYPO3\CMS\Core\SingletonInterface;

class ConfigurationCommandController extends CommandController implements SingletonInterface
{
    /**
     * @var ConfigurationService
     */
    protected $configurationService;

    /**
     * @var ConsoleRenderer
     */
    protected $consoleRenderer;

    public function __construct(ConfigurationService $configurationService, ConsoleRenderer $consoleRenderer)
    {
        $this->configurationService = $configurationService;
        $this->consoleRenderer = $consoleRenderer;
    }

    /**
     * Remove configuration option
     *
     * Removes a system configuration option by path.
     *
     * For this command to succeed, the configuration option(s) must be in
     * LocalConfiguration.php and not be overridden elsewhere.
     *
     * Example: %command.full_name% DB,EXT/EXTCONF/realurl
     *
     * @param array $paths Path to system configuration that should be removed. Multiple paths can be specified separated by comma
     * @param bool $force If set, does not ask for confirmation
     */
    public function removeCommand(array $paths, $force = false)
    {
        foreach ($paths as $path) {
            if (!$this->configurationService->localIsActive($path)) {
                $this->outputLine('<warning>It seems that configuration for path "%s" is overridden.</warning>', [$path]);
                $this->outputLine('<warning>Removing the new value might have no effect.</warning>');
            }
            if (!$force && $this->configurationService->hasLocal($path)) {
                $reallyDelete = $this->output->askConfirmation('Remove ' . $path . ' from system configuration (TYPO3_CONF_VARS)? (yes/no): ', false);
                if (!$reallyDelete) {
                    continue;
                }
            }
            $removed = $this->configurationService->removeLocal($path);
            if ($removed) {
                $this->outputLine('<info>Removed "%s" from system configuration.</info>', [$path]);
            } else {
                $this->outputLine('<warning>Path "%s" seems invalid or empty. Nothing done!</warning>', [$path]);
            }
        }
    }

    /**
     * Show configuration value
     *
     * Shows system configuration value by path.
     * If the currently active configuration differs from the value in LocalConfiguration.php
     * the difference between these values is shown.
     *
     * Example: %command.full_name% DB
     *
     * @param string $path Path to system configuration option
     */
    public function showCommand($path)
    {
        $hasActive = $this->configurationService->hasActive($path);
        $hasLocal = $this->configurationService->hasLocal($path);
        if (!$hasActive && !$hasLocal) {
            $this->outputLine('<error>No configuration found for path "%s"</error>', [$path]);
            $this->quit(1);
        }
        $active = null;
        if ($hasActive) {
            $active = $this->configurationService->getActive($path);
        }
        if ($this->configurationService->localIsActive($path) && $hasActive) {
            $this->outputLine($this->consoleRenderer->render($active));
        } else {
            $local = null;
            if ($hasLocal) {
                $local = $this->configurationService->getLocal($path);
            }
            $this->outputLine($this->consoleRenderer->renderDiff($local, $active));
        }
    }

    /**
     * Show active configuration value
     *
     * Shows active system configuration by path.
     * Shows the configuration value that is currently effective, no matter where and how it is set.
     *
     * Example: %command.full_name% DB --json
     *
     * @param string $path Path to system configuration
     * @param bool $json If set, the configuration is shown as JSON
     */
    public function showActiveCommand($path, $json = false)
    {
        if (!$this->configurationService->hasActive($path)) {
            $this->outputLine('<error>No configuration found for path "%s"</error>', [$path]);
            $this->quit(1);
        }
        $active = $this->configurationService->getActive($path);
        $this->outputLine($this->consoleRenderer->render($active, $json));
    }

    /**
     * Show local configuration value
     *
     * Shows local configuration option value by path.
     * Shows the value which is stored in LocalConfiguration.php.
     * Note that this value could be overridden. Use typo3cms configuration:show <path> to see if this is the case.
     *
     * Example: %command.full_name% DB
     *
     * @param string $path Path to local system configuration
     * @param bool $json If set, the configuration is shown as JSON
     * @see typo3_console:configuration:show
     */
    public function showLocalCommand($path, $json = false)
    {
        if (!$this->configurationService->hasLocal($path)) {
            $this->outputLine('<error>No configuration found for path "%s"</error>', [$path]);
            $this->quit(1);
        }
        $active = $this->configurationService->getLocal($path);
        $this->outputLine($this->consoleRenderer->render($active, $json));
    }

    /**
     * Set configuration value
     *
     * Set system configuration option value by path.
     *
     * Examples:
     * %command.full_name% SYS/fileCreateMask 0664
     * %command.full_name% EXTCONF/processor_enabled true --json
     * %command.full_name% EXTCONF/lang/availableLanguages '["de", "fr"]' --json
     *
     * @param string $path Path to system configuration
     * @param string $value Value for system configuration
     * @param bool $json Treat value as JSON (also makes it possible to force datatypes for value)
     */
    public function setCommand($path, $value, $json = false)
    {
        if (!$this->configurationService->localIsActive($path)) {
            $this->outputLine('<warning>It seems that configuration for path "%s" is overridden.</warning>', [$path]);
            $this->outputLine('<warning>Writing the new value might have no effect.</warning>');
        }

        $encodedValue = $value;
        if ($json) {
            $encodedValue = @json_decode($value, true);
        }

        if ($encodedValue === null && strtolower($value) !== 'null') {
            $this->outputLine('<error>Could not decode value "%s" as json.</error>', [$value]);
            $this->quit(2);
        }

        $setWasAllowed = $this->configurationService->setLocal($path, $encodedValue);
        $isApplied = $this->configurationService->hasLocal($path);

        if (!$setWasAllowed) {
            $this->outputLine('<warning>Could not set value "%s" for configuration path "%s".</warning>', [$value, $path]);
            $this->outputLine('<warning>Possible reasons: configuration path is not allowed, configuration is not writable or type of value does not match given type.</warning>', [$value, $path]);
            $this->quit(1);
        }
        if ($isApplied) {
            $this->outputLine('<info>Successfully set value for path "%s".</info>', [$path]);
        } else {
            $this->outputLine('<warning>Value "%s" for configuration path "%s" seems not applied.</warning>', [$value, $path]);
            $this->outputLine('<warning>Possible reasons: changed value in AdditionalConfiguration.php or extension ext_localconf.php</warning>');
        }
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Hook;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Mvc\Cli\Symfony\Application;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Hook
 */
class ExtensionInstallation
{
    const BINARY_PATH = 'typo3conf/ext/typo3_console/';
    const COPY_FAILED_MESSAGE_TITLE = 'Could not copy %s script to TYPO3 root directory (%s)!';
    const COPY_FAILED_MESSAGE = 'Check the permissions of your root directory. Is there a file or directory named %s inside this directory?';
    const COPY_SUCCESS_MESSAGE = 'Successfully copied the %s script to TYPO3 root directory. Let\'s dance!';
    const EXTKEY = 'typo3_console';
    const EM_FLASH_MESSAGE_QUEUE_ID = 'extbase.flashmessages.tx_extensionmanager_tools_extensionmanagerextensionmanager';

    /**
     * Actions to take after extension has been installed
     *
     * @param string $keyOfInstalledExtension
     */
    public function afterInstallation($keyOfInstalledExtension)
    {
        if (self::EXTKEY !== $keyOfInstalledExtension) {
            return;
        }
        $scriptName = $this->isWindowsOs() ? 'Scripts/' . Application::COMMAND_NAME . '.bat' : Application::COMMAND_NAME;
        $success = $this->safeCopy(PATH_site . self::BINARY_PATH . $scriptName, PATH_site . basename($scriptName));
        if (!$success) {
            self::addFlashMessage(sprintf(self::COPY_FAILED_MESSAGE, $scriptName), sprintf(self::COPY_FAILED_MESSAGE_TITLE, $scriptName, PATH_site), AbstractMessage::WARNING);
        } else {
            self::addFlashMessage(sprintf(self::COPY_SUCCESS_MESSAGE, $scriptName));
        }
    }

    /**
     * Creates a Message object and adds it to the FlashMessageQueue.
     *
     * @param string $messageBody The message
     * @param string $messageTitle Optional message title
     * @param int $severity Optional severity, must be one of \TYPO3\CMS\Core\Messaging\FlashMessage constants
     * @param bool $storeInSession Optional, defines whether the message should be stored in the session (default) or not
     * @throws \TYPO3\CMS\Core\Exception
     * @throws \InvalidArgumentException if the message body is no string
     * @return void
     */
    protected function addFlashMessage($messageBody, $messageTitle = '', $severity = AbstractMessage::OK, $storeInSession = true)
    {
        if (PHP_SAPI === 'cli') {
            return;
        }
        if (!is_string($messageBody)) {
            throw new \InvalidArgumentException('The message body must be of type string, "' . gettype($messageBody) . '" given.', 1418250286);
        }
        $flashMessage = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessage::class, $messageBody, $messageTitle, $severity, $storeInSession);
        $queue = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageQueue::class, self::EM_FLASH_MESSAGE_QUEUE_ID);
        $queue->enqueue($flashMessage);
    }

    /**
     * Copy typo3 console binary to root directory taking several possible situations into account
     *
     * @param string $fullSourcePath Path to the script that should be copied (depending on OS)
     * @param string $fullTargetPath Target path to which the script should be copied to
     * @param string $relativeWebDir Relative path to the web directory (which equals the TYPO3 root directory currently)
     * @return bool
     * @internal
     */
    private function safeCopy($fullSourcePath, $fullTargetPath, $relativeWebDir = '')
    {
        if (file_exists($fullTargetPath)) {
            if (!is_file($fullTargetPath)) {
                // Seems to be a directory: ignore
                return false;
            }
            if (!self::isTypo3CmsBinary($fullTargetPath)) {
                // File is there but does not seem to be a previous version of our script: better ignore
                return false;
            }
        }
        if ($this->isWindowsOs()) {
            $success = @copy($fullSourcePath, $fullTargetPath);
        } else {
            $proxyFileContent = file_get_contents($fullSourcePath);
            $proxyFileContent = str_replace(
                'require __DIR__ . \'/Scripts/typo3-console.php\';',
                '// In non Composer mode we\'re copied into TYPO3 web root
require __DIR__ . \'/typo3conf/ext/typo3_console/Scripts/typo3-console.php\';',
                $proxyFileContent
            );
            $success = file_put_contents($fullTargetPath, $proxyFileContent);
        }

        if ($success && !$this->isWindowsOs()) {
            $success = @chmod($fullTargetPath, 0755);
        }
        if ($success) {
            $success = @file_put_contents(
                $fullTargetPath,
                str_replace(
                    '{$relative-web-dir}',
                    $relativeWebDir,
                    file_get_contents($fullTargetPath)
                )
            );
        }

        return $success;
    }

    protected static function isTypo3CmsBinary($fullTargetPath)
    {
        $binaryContent = file_get_contents($fullTargetPath);

        return strpos($binaryContent, 'typo3cms.php') !== false
            || strpos($binaryContent, 'typo3-console.php') !== false;
    }

    /**
     * Returns true if PHP runs on Windows OS
     *
     * @return bool
     */
    private function isWindowsOs()
    {
        if (DIRECTORY_SEPARATOR === '\\') {
            return true;
        }

        return false;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Command\Cache;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Command\AbstractConvertedCommand;
use Helhum\Typo3Console\Core\Booting\RunLevel;
use Helhum\Typo3Console\Mvc\Cli\Symfony\Application;
use Helhum\Typo3Console\Service\CacheLowLevelCleaner;
use Helhum\Typo3Console\Service\CacheService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class CacheFlushCommand extends AbstractConvertedCommand
{
    protected function configure()
    {
        $this->setDescription('Flush all caches');
        $this->setHelp(
            <<<'EOH'
Flushes TYPO3 core caches first and after that, flushes caches from extensions.
EOH
        );

        $this->setDefinition($this->createCompleteInputDefinition());
    }

    protected function createNativeDefinition(): array
    {
        return [
            new InputOption(
                'files-only',
                null,
                InputOption::VALUE_NONE,
                'Only file caches are flushed'
            ),
        ];
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $filesOnly = $input->getOption('files-only');
        $application = $this->getApplication();
        if (!$application instanceof Application) {
            throw new \RuntimeException('Fatal error. Application is not properly initialized.', 1546617606);
        }
        $filesOnly = $filesOnly || !$application->isFullyCapable();

        $io = new SymfonyStyle($input, $output);

        $lowLevelCleaner = new CacheLowLevelCleaner();
        $lowLevelCleaner->forceFlushCachesFiles();
        if ($filesOnly) {
            $io->writeln('Flushed all file caches.');
            // No need to proceed, as files only flush is requested
            return;
        }

        $lowLevelCleaner->forceFlushDatabaseCacheTables();
        $application->boot(RunLevel::LEVEL_FULL);

        $cacheService = new CacheService();
        $cacheService->flush();
        $cacheService->flushCachesWithDataHandler();

        $io->writeln('Flushed all caches.');
    }

    /**
     * @deprecated will be removed with 6.0
     *
     * @return array
     */
    protected function createDeprecatedDefinition(): array
    {
        return [
            new InputOption(
                'force',
                null,
                InputOption::VALUE_NONE,
                'Cache is forcibly flushed (low level operations are performed)'
            ),
            new InputArgument(
                'force',
                null,
                'Cache is forcibly flushed (low level operations are performed)',
                false
            ),
            new InputArgument(
                'filesOnly',
                null,
                'Only file caches are flushed',
                false
            ),
        ];
    }

    /**
     * @deprecated will be removed with 6.0
     */
    protected function handleDeprecatedArgumentsAndOptions(InputInterface $input, OutputInterface $output)
    {
        if ($input->getOption('force')) {
            $io = new SymfonyStyle($input, $output);
            $io->getErrorStyle()->writeln('<warning>Using "--force" is deprecated and has no effect any more.</warning>');
        }
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Command;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Mvc\Controller\CommandController;
use Helhum\Typo3Console\Service;
use Helhum\Typo3Console\Service\XsdGenerator;
use TYPO3\CMS\Core\SingletonInterface;

/**
 * Command controller for Fluid documentation rendering
 *
 * @deprecated will be removed in TYPO3 v6.0 as it is not in use anymore, see Documentation/GenerateXsdCommand class.
 */
class DocumentationCommandController extends CommandController implements SingletonInterface
{
    /**
     * @var XsdGenerator
     */
    protected $xsdGenerator;

    public function __construct(XsdGenerator $xsdGenerator)
    {
        $this->xsdGenerator = $xsdGenerator;
    }

    /**
     * Generate Fluid ViewHelper XSD Schema
     *
     * Generates Schema documentation (XSD) for your ViewHelpers, preparing the
     * file to be placed online and used by any XSD-aware editor.
     * After creating the XSD file, reference it in your IDE and import the namespace
     * in your Fluid template by adding the xmlns:* attribute(s):
     *
     *
     * @param string $phpNamespace Namespace of the Fluid ViewHelpers without leading backslash (for example 'TYPO3\Fluid\ViewHelpers' or 'Tx_News_ViewHelpers'). NOTE: Quote and/or escape this argument as needed to avoid backslashes from being interpreted!
     * @param string $xsdNamespace Unique target namespace used in the XSD schema (for example "http://yourdomain.org/ns/viewhelpers"). Defaults to "http://typo3.org/ns/<php namespace>".
     * @param string $targetFile File path and name of the generated XSD schema. If not specified the schema will be output to standard output.
     * @return void
     */
    public function generateXsdCommand($phpNamespace, $xsdNamespace = null, $targetFile = null)
    {
        if ($xsdNamespace === null) {
            $phpNamespace = rtrim($phpNamespace, '_\\');
            if (strpos($phpNamespace, '\\') === false) {
                $search = ['Tx_', '_'];
                $replace = ['', '/'];
            } else {
                $search = '\\';
                $replace = '/';
            }
            $xsdNamespace = sprintf('http://typo3.org/ns/%s', str_replace($search, $replace, $phpNamespace));
        }
        $xsdSchema = '';
        try {
            $xsdSchema = $this->xsdGenerator->generateXsd($phpNamespace, $xsdNamespace);
        } catch (Service\Exception $exception) {
            $this->outputLine('An error occurred while trying to generate the XSD schema:');
            $this->outputLine('%s', [$exception->getMessage()]);
            $this->quit(1);
        }
        if ($targetFile === null) {
            echo $xsdSchema;
        } else {
            file_put_contents($targetFile, $xsdSchema);
        }
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Core\Booting;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Error\ErrorHandler;
use Helhum\Typo3Console\Error\ExceptionHandler;
use Helhum\Typo3Console\Property\TypeConverter\ArrayConverter;
use Symfony\Component\Console\Exception\RuntimeException;
use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Property\TypeConverter\BooleanConverter;
use TYPO3\CMS\Extbase\Property\TypeConverter\FloatConverter;
use TYPO3\CMS\Extbase\Property\TypeConverter\IntegerConverter;
use TYPO3\CMS\Extbase\Property\TypeConverter\StringConverter;

class Scripts
{
    /**
     * @param Bootstrap $bootstrap
     */
    public static function initializeConfigurationManagement(Bootstrap $bootstrap)
    {
        $bootstrap->populateLocalConfiguration();
        \Closure::bind(function () use ($bootstrap) {
            $method = 'initializeRuntimeActivatedPackagesFromConfiguration';
            if (!CompatibilityScripts::isComposerMode()) {
                $bootstrap->$method(GeneralUtility::makeInstance(PackageManager::class));
            }
            $method = 'setDefaultTimezone';
            $bootstrap->$method();
        }, null, $bootstrap)();
        CompatibilityScripts::initializeConfigurationManagement($bootstrap);
    }

    public static function baseSetup(Bootstrap $bootstrap)
    {
        define('TYPO3_MODE', 'BE');
        define('PATH_site', \TYPO3\CMS\Core\Utility\GeneralUtility::fixWindowsFilePath(getenv('TYPO3_PATH_ROOT')) . '/');
        define('PATH_thisScript', PATH_site . 'typo3/index.php');

        $bootstrap->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI);
        $bootstrap->baseSetup();
        // Mute notices
        error_reporting(E_ALL & ~E_NOTICE);
        $exceptionHandler = new ExceptionHandler();
        set_exception_handler([$exceptionHandler, 'handleException']);

        self::initializePackageManagement($bootstrap);
    }

    /**
     * Initializes the package system and loads the package configuration and settings
     * provided by the packages.
     *
     * @return void
     */
    private static function initializePackageManagement(Bootstrap $bootstrap)
    {
        $packageManager = CompatibilityScripts::createPackageManager();
        $bootstrap->setEarlyInstance(PackageManager::class, $packageManager);
        GeneralUtility::setSingletonInstance(PackageManager::class, $packageManager);
        ExtensionManagementUtility::setPackageManager($packageManager);
        $packageManager->init();
    }

    public static function initializeErrorHandling()
    {
        $errorHandler = new ErrorHandler();
        $errorHandler->setExceptionalErrors([E_WARNING, E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE, E_RECOVERABLE_ERROR]);
        set_error_handler([$errorHandler, 'handleError']);
    }

    public static function initializeDisabledCaching(Bootstrap $bootstrap)
    {
        self::initializeCachingFramework($bootstrap, true);
    }

    public static function initializeCaching(Bootstrap $bootstrap)
    {
        self::initializeCachingFramework($bootstrap);
    }

    private static function initializeCachingFramework(Bootstrap $bootstrap, bool $disableCaching = false)
    {
        $cacheManager = CompatibilityScripts::createCacheManager($disableCaching);
        \TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManager);
        $bootstrap->setEarlyInstance(CacheManager::class, $cacheManager);
    }

    /**
     * @param Bootstrap $bootstrap
     */
    public static function initializeExtensionConfiguration(Bootstrap $bootstrap)
    {
        CompatibilityScripts::initializeExtensionConfiguration($bootstrap);
        ExtensionManagementUtility::loadExtLocalconf();
        $bootstrap->setFinalCachingFrameworkCacheConfiguration();
        $bootstrap->unsetReservedGlobalVariables();
    }

    public static function initializePersistence()
    {
        ExtensionManagementUtility::loadBaseTca();
    }

    /**
     * @param Bootstrap $bootstrap
     */
    public static function initializeAuthenticatedOperations(Bootstrap $bootstrap)
    {
        $bootstrap->loadExtTables();
        $bootstrap->initializeBackendUser(CommandLineUserAuthentication::class);
        self::loadCommandLineBackendUser();
        // Global language object on CLI? rly? but seems to be needed by some scheduler tasks :(
        $bootstrap->initializeLanguageObject();
    }

    /**
     * If the backend script is in CLI mode, it will try to load a backend user named _cli_lowlevel
     *
     * @throws RuntimeException if a non-admin Backend user could not be loaded
     */
    private static function loadCommandLineBackendUser()
    {
        /** @var CommandLineUserAuthentication $backendUser */
        $backendUser = $GLOBALS['BE_USER'];
        if ($backendUser->user['uid']) {
            throw new RuntimeException('Another user was already loaded which is impossible in CLI mode!', 3);
        }
        $backendUser->authenticate();
    }

    /**
     * Provide cleaned implementation of TYPO3 core classes.
     * Can only be called *after* extension configuration is loaded (needs extbase configuration)!
     */
    public static function provideCleanClassImplementations()
    {
        self::overrideImplementation(\TYPO3\CMS\Extbase\Command\HelpCommandController::class, \Helhum\Typo3Console\Command\HelpCommandController::class);
        self::overrideImplementation(\TYPO3\CMS\Extbase\Mvc\Cli\Command::class, \Helhum\Typo3Console\Mvc\Cli\Command::class);

        // @deprecated can be removed once command controller support is removed
        if (empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])) {
            $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] = [];
        }
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = ArrayConverter::class;
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = StringConverter::class;
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = BooleanConverter::class;
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = IntegerConverter::class;
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = FloatConverter::class;
    }

    /**
     * Tell Extbase, TYPO3 and PHP that we have another implementation
     *
     * @param string $originalClassName
     * @param string $overrideClassName
     */
    public static function overrideImplementation($originalClassName, $overrideClassName)
    {
        self::registerImplementation($originalClassName, $overrideClassName);
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$originalClassName]['className'] = $overrideClassName;
        class_alias($overrideClassName, $originalClassName);
    }

    /**
     * Tell Extbase about this implementation
     *
     * @param string $className
     * @param string $alternativeClassName
     */
    private static function registerImplementation($className, $alternativeClassName)
    {
        /** @var $extbaseObjectContainer \TYPO3\CMS\Extbase\Object\Container\Container */
        $extbaseObjectContainer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class);
        $extbaseObjectContainer->registerImplementation($className, $alternativeClassName);
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Service\Configuration;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Class ConfigurationService
 */
class ConfigurationService implements SingletonInterface
{
    const EXCEPTION_CODE_ARRAY_KEY_NOT_FOUND = 1341397869;

    /**
     * @var \TYPO3\CMS\Core\Configuration\ConfigurationManager
     */
    protected $configurationManager;

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

    /**
     * ConfigurationService constructor.
     *
     * @param ConfigurationManager $configurationManager
     * @param array $activeConfiguration
     */
    public function __construct(ConfigurationManager $configurationManager = null, array $activeConfiguration = [])
    {
        $this->configurationManager = $configurationManager ?: GeneralUtility::makeInstance(ConfigurationManager::class);
        $this->activeConfiguration = $activeConfiguration ?: $GLOBALS['TYPO3_CONF_VARS'];
    }

    /**
     * @param string $path
     * @return bool
     */
    public function hasDefault($path)
    {
        return $this->has($path, $this->configurationManager->getDefaultConfiguration());
    }

    /**
     * @param string $path
     * @return bool
     */
    public function hasLocal($path)
    {
        return $this->has($path, $this->getMergedConfiguration());
    }

    /**
     * @param string $path
     * @return bool
     */
    public function hasActive($path)
    {
        return $this->has($path, $this->activeConfiguration);
    }

    /**
     * @param string $path
     * @throws ConfigurationValueNotFoundException
     * @return mixed
     */
    public function getDefault($path)
    {
        return $this->get($path, $this->configurationManager->getDefaultConfiguration());
    }

    /**
     * @param string $path
     * @throws ConfigurationValueNotFoundException
     * @return mixed
     */
    public function getLocal($path)
    {
        return $this->get($path, $this->getMergedConfiguration());
    }

    /**
     * @param string $path
     * @throws ConfigurationValueNotFoundException
     * @return mixed
     */
    public function getActive($path)
    {
        return $this->get($path, $this->activeConfiguration);
    }

    /**
     * @param string $path
     * @param array $config
     * @return bool
     */
    protected function has($path, array $config)
    {
        try {
            ArrayUtility::getValueByPath($config, $path);
        } catch (\RuntimeException $e) {
            if ($e->getCode() !== self::EXCEPTION_CODE_ARRAY_KEY_NOT_FOUND) {
                throw $e;
            }

            return false;
        }

        return true;
    }

    /**
     * @param string $path
     * @param array $config
     * @throws ConfigurationValueNotFoundException
     * @return mixed
     */
    protected function get($path, array $config)
    {
        try {
            $value = ArrayUtility::getValueByPath($config, $path);
        } catch (\RuntimeException $e) {
            if ($e->getCode() !== self::EXCEPTION_CODE_ARRAY_KEY_NOT_FOUND) {
                throw $e;
            }
            throw new ConfigurationValueNotFoundException('No value found for this key!', 1461607530);
        }

        return $value;
    }

    /**
     * @param string $path
     * @return bool
     */
    public function removeLocal($path)
    {
        return $this->configurationManager->removeLocalConfigurationKeysByPath([$path]);
    }

    /**
     * Sets a value in LocalConfiguration.php
     *
     * But only if types are compatible and local config is active
     *
     * @param string $path
     * @param mixed $value
     * @param string $targetType
     * @return bool
     */
    public function setLocal($path, $value, $targetType = '')
    {
        try {
            $value = $this->convertToTargetType($path, $value, $targetType);

            return $this->configurationManager->setLocalConfigurationValueByPath($path, $value);
        } catch (TypesAreNotConvertibleException $e) {
            return false;
        }
    }

    /**
     * Returns true if the value is stored in the LocalConfiguration.php file and
     * is NOT overridden later (e.g. in AdditionalConfiguration.php)
     *
     * @param string $path
     * @return bool
     */
    public function localIsActive($path)
    {
        if ($this->hasLocal($path)) {
            return $this->hasActive($path) && $this->getLocal($path) === $this->getActive($path);
        }

        return !$this->hasActive($path);
    }

    /**
     * Convert a value to the type belonging to the given path
     *
     * @param string $path
     * @param string $value
     * @param string $targetType
     * @throws TypesAreNotConvertibleException
     * @return bool|float|int|string
     */
    public function convertToTargetType($path, $value, $targetType = '')
    {
        $targetType = $targetType ?: $this->getType($path);
        $actualType = gettype($value);
        if ($actualType !== $targetType && $targetType !== 'NULL') {
            if ($this->isTypeConvertible($targetType, $actualType)) {
                switch ($targetType) {
                    case 'integer':
                        $value = (int)$value;
                        break;
                    case 'float':
                    case 'double':
                        $value = (float)$value;
                        break;
                    case 'boolean':
                        $value = (bool)$value;
                        break;
                    case 'string':
                        $value = (string)$value;
                        break;
                    default:
                        // We don't know any type conversion, so we better exit
                        throw new TypesAreNotConvertibleException(sprintf('Unknown target type "%s"', $targetType), 1477778705);
                }
            } else {
                // We cannot convert from or to non scalar types, so we better exit
                throw new TypesAreNotConvertibleException(sprintf('Cannot convert type from "%s" to "%s"', $actualType, $targetType), 1477778754);
            }
        }

        return $value;
    }

    /**
     * Returns the type of a value in given config path
     *
     * @param string $path
     * @return string
     */
    private function getType($path)
    {
        $value = null;
        if ($this->hasActive($path)) {
            $value = $this->getActive($path);
        }
        if ($this->hasLocal($path)) {
            $value = $this->getLocal($path);
        }
        if ($this->hasDefault($path)) {
            $value = $this->getDefault($path);
        }

        return gettype($value);
    }

    /**
     * Checks if target and actual type is scalar
     *
     * @param string $targetType
     * @param string $actualType
     * @return bool
     */
    private function isTypeConvertible($targetType, $actualType)
    {
        if (in_array($targetType, ['array', 'object', 'resource'], true)) {
            return false;
        }
        if (in_array($actualType, ['array', 'object', 'resource'], true)) {
            return false;
        }

        return true;
    }

    /**
     * @return array
     */
    protected function getMergedConfiguration()
    {
        $mergedConfig = $this->configurationManager->getDefaultConfiguration();
        ArrayUtility::mergeRecursiveWithOverrule($mergedConfig, $this->configurationManager->getLocalConfiguration());

        return $mergedConfig;
    }
}


<?php
declare(strict_types=1);
namespace Helhum\Typo3Console\Mvc\Cli\Symfony;

/*
 * This file is part of the TYPO3 Console project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read
 * LICENSE file that was distributed with this source code.
 *
 */

use Helhum\Typo3Console\Core\Booting\RunLevel;
use Helhum\Typo3Console\Core\Booting\StepFailedException;
use Helhum\Typo3Console\Error\ExceptionRenderer;
use Helhum\Typo3Console\Exception\CommandNotAvailableException;
use Helhum\Typo3Console\Mvc\Cli\Symfony\Command\HelpCommand;
use Helhum\Typo3Console\Mvc\Cli\Symfony\Command\ListCommand;
use Helhum\Typo3Console\Mvc\Cli\Symfony\Input\ArgvInput;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Represents the complete console application
 */
class Application extends BaseApplication
{
    const TYPO3_CONSOLE_VERSION = '5.6.0';
    const COMMAND_NAME = 'typo3cms';

    /**
     * @var RunLevel
     */
    private $runLevel;

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

    public function __construct(RunLevel $runLevel = null, bool $composerManaged = true)
    {
        parent::__construct('TYPO3 Console', self::TYPO3_CONSOLE_VERSION);
        $this->runLevel = $runLevel;
        $this->composerManaged = $composerManaged;
        $this->setAutoExit(false);
    }

    /**
     * Whether the application can run all commands
     * It will return false when TYPO3 is not fully set up yet,
     * e.g. directly after a composer install
     *
     * @return bool
     */
    public function isFullyCapable(): bool
    {
        return $this->runLevel->getMaximumAvailableRunLevel() === RunLevel::LEVEL_FULL;
    }

    /**
     * Whether errors occurred during booting
     *
     * @return bool
     */
    public function hasErrors(): bool
    {
        return $this->runLevel->getError() !== null;
    }

    /**
     * @param string $runLevel
     * @throws StepFailedException
     */
    public function boot(string $runLevel)
    {
        $this->runLevel->runSequence($runLevel);
    }

    /**
     * Whether this application is composer managed.
     * Can be used to enable or disable commands or arguments/ options
     *
     * @return bool
     */
    public function isComposerManaged(): bool
    {
        return $this->composerManaged;
    }

    /**
     * Checks if the given command can be executed in current application state
     *
     * @param Command $command
     * @return bool
     */
    public function isCommandAvailable(Command $command): bool
    {
        if (!$this->isFullyCapable()
            && in_array($command->getName(), [
                // Although these commands are technically available
                // they call other hidden commands in sub processes
                // that need all capabilities. Therefore we disable these commands here.
                // This can be removed, once they implement Symfony commands directly.
                'upgrade:all',
                'upgrade:list',
                'upgrade:wizard',
            ], true)
        ) {
            return false;
        }
        if ($command->getName() === 'cache:flushcomplete') {
            return true;
        }

        return $this->runLevel->isCommandAvailable($command->getName());
    }

    public function renderException(\Exception $exception, OutputInterface $output)
    {
        if ($exception instanceof CommandNotAvailableException) {
            $helper = new SymfonyStyle(new ArgvInput(), $output);
            $helper->getErrorStyle()->block(
                [
                    sprintf(
                        'Command "%s" cannot be run, because it needs a fully set up TYPO3 system.'
                        . PHP_EOL
                        . 'Your system currently lacks essential configuration files (LocalConfiguration.php, PackageStates.php).',
                        $exception->getCommandName()
                    ),
                    'Try setting up your system using the "install:setup" command.',
                ],
                null,
                'fg=white;bg=red',
                ' ',
                true
            );

            return;
        }

        (new ExceptionRenderer())->render($exception, $output, $this);
    }

    /**
     * @param Command $command
     * @param InputInterface $input
     * @param OutputInterface $output
     * @throws \Throwable
     * @return int
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        $this->ensureCommandAvailable($command);
        $this->ensureStableEnvironmentForCommand($command, $output->isVerbose());

        $exitCode = parent::doRunCommand($command, $input, $output);

        if ($bootingError = $this->runLevel->getError()) {
            $messages = [
                sprintf(
                    'An error occurred while executing "%s".',
                    str_replace('helhum.typo3console:', '', $bootingError->getFailedStep()->getIdentifier())
                ),
                sprintf(
                    'Run "%s --verbose" to see a detailed error message.',
                    $_SERVER['PHP_SELF']
                ),
            ];

            $helper = new SymfonyStyle($input, $output);
            $helper->getErrorStyle()->block(
                $messages,
                null,
                'fg=black;bg=yellow',
                ' ',
                true
            );
        }

        return $exitCode;
    }

    private function ensureCommandAvailable(Command $command)
    {
        if (!$this->runLevel->getError() && !$this->isCommandAvailable($command)) {
            throw new CommandNotAvailableException($command->getName());
        }
    }

    private function ensureStableEnvironmentForCommand(Command $command, bool $environmentIsVerbose)
    {
        $bootingError = $this->runLevel->getError();
        if ($bootingError && ($environmentIsVerbose || !$this->runLevel->isInternalCommand($command->getName()))) {
            throw $bootingError->getPrevious();
        }
    }

    protected function getDefaultInputDefinition()
    {
        return new InputDefinition([
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),

            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
        ]);
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @throws LogicException
     * @return Command[] An array of default Command instances
     */
    protected function getDefaultCommands(): array
    {
        return [new HelpCommand(), new ListCommand()];
    }

    /**
     * Add default set of styles to the output
     *
     * @param InputInterface $input
     * @param OutputInterface $output
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        parent::configureIO($input, $output);
        $output->getFormatter()->setStyle('b', new OutputFormatterStyle(null, null, ['bold']));
        $output->getFormatter()->setStyle('i', new OutputFormatterStyle('black', 'white'));
        $output->getFormatter()->setStyle('u', new OutputFormatterStyle(null, null, ['underscore']));
        $output->getFormatter()->setStyle('em', new OutputFormatterStyle(null, null, ['reverse']));
        $output->getFormatter()->setStyle('strike', new OutputFormatterStyle(null, null, ['conceal']));
        $output->getFormatter()->setStyle('success', new OutputFormatterStyle('green'));
        $output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
        $output->getFormatter()->setStyle('ins', new OutputFormatterStyle('green'));
        $output->getFormatter()->setStyle('del', new OutputFormatterStyle('red'));
        $output->getFormatter()->setStyle('code', new OutputFormatterStyle(null, null, ['bold']));
    }
}