diff --git a/cli/Commands/AbstractCommand.php b/cli/Commands/AbstractCommand.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3ce3cca185563fb74133b43c8163505693b80da
--- /dev/null
+++ b/cli/Commands/AbstractCommand.php
@@ -0,0 +1,72 @@
+<?php
+namespace Studip\Cli\Commands;
+
+use FilesystemIterator;
+use Iterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RecursiveRegexIterator;
+use RegexIterator;
+use Symfony\Component\Console\Command\Command;
+
+abstract class AbstractCommand extends Command
+{
+    /**
+     * Returns a folder iterator accessing all files inside that folder.
+     *
+     * @param string     $folder     Folder to return iterator for
+     * @param bool       $recursive  Recurse into subfolders as well
+     * @param array|null $extensions Optional list of extensions for files to be returned
+     *
+     * @return Iterator
+     */
+    protected function getFolderIterator(string $folder, bool $recursive = false, ?array $extensions = null): Iterator
+    {
+        if ($recursive) {
+            $iterator = new RecursiveDirectoryIterator(
+                $folder,
+                FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS
+            );
+            $iterator = new RecursiveIteratorIterator($iterator);
+        } else {
+            $iterator = new FilesystemIterator($folder);
+        }
+
+        if ($extensions) {
+            $extensions = array_map(function ($extension) {
+                return preg_quote($extension, '/');
+            }, $extensions);
+            $iterator = new RegexIterator(
+                $iterator,
+                '/\.(?:' . implode('|', $extensions) . ')$/',
+                RecursiveRegexIterator::MATCH
+            );
+        }
+
+        return $iterator;
+    }
+
+    protected function relativeFilePath(string $filepath, bool $plugin = false): string
+    {
+        $filepath = str_replace($GLOBALS['STUDIP_BASE_PATH'] . '/', '', $filepath);
+
+        if ($plugin) {
+            $filepath = str_replace('public/plugins_packages/', '', $filepath);
+        }
+
+        return $filepath;
+    }
+
+    protected function absoluteFilePath(string $filepath, bool $plugin = false): string
+    {
+        if ($plugin && mb_strpos($filepath, 'public/plugins_packages') === false) {
+            $filepath = 'public/plugins_packages/' . ltrim($filepath, '/');
+        }
+
+        if (mb_strpos($filepath, $GLOBALS['STUDIP_BASE_PATH']) === false) {
+            $filepath = $GLOBALS['STUDIP_BASE_PATH'] . '/' . ltrim($filepath, '/');
+        }
+
+        return $filepath;
+    }
+}
diff --git a/cli/Commands/AbstractPluginCommand.php b/cli/Commands/AbstractPluginCommand.php
new file mode 100644
index 0000000000000000000000000000000000000000..149f54c231aef1afa2d0af7dcd5ecc7df5bfd134
--- /dev/null
+++ b/cli/Commands/AbstractPluginCommand.php
@@ -0,0 +1,21 @@
+<?php
+namespace Studip\Cli\Commands;
+
+abstract class AbstractPluginCommand extends AbstractCommand
+{
+    protected function findPluginByName(\PluginManager $pluginManager, string $pluginname): ?array
+    {
+        $plugins = $pluginManager->getPluginInfos();
+        $found = array_filter($plugins, function ($plugin) use ($pluginname) {
+            return mb_strtolower($pluginname) === mb_strtolower($plugin['name']);
+        });
+
+        return count($found) ? reset($found) : null;
+    }
+
+    protected function findPluginNameByFolder(string $folder)
+    {
+        var_dump('foo');die;
+        return 'foo';
+    }
+}
diff --git a/cli/Commands/Base/Dump.php b/cli/Commands/Base/Dump.php
new file mode 100644
index 0000000000000000000000000000000000000000..39be0a27f5f57f46fd04c94b03ed13ec2b8d436f
--- /dev/null
+++ b/cli/Commands/Base/Dump.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Studip\Cli\Commands\Base;
+
+use Config;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class Dump extends Command
+{
+    protected static $defaultName = 'base:dump';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Dumping Stud.IP directory');
+        $this->addArgument('path', InputArgument::REQUIRED, 'path where the backup should be saved');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+
+        $dump_dir = realpath($input->getArgument('path'));
+        $prefix = Config::get()->STUDIP_INSTALLATION_ID ? Config::get()->STUDIP_INSTALLATION_ID : 'studip';
+        $today = date('Ymd');
+
+        $base_path = realpath($GLOBALS['STUDIP_BASE_PATH']);
+
+        if (!$base_path) {
+            $io->error('Stud.IP directory not found!');
+            return Command::FAILURE;
+        }
+
+        $dumb_studip = $dump_dir . '/' . $prefix . '-BASE-' . $today . '.tar.gz';
+
+        $io->info('Dumping Stud.IP directory to' . $base_path);
+
+        $cmd = "cd $base_path && tar -czf $dumb_studip ." . ' 2>&1';
+
+        exec($cmd, $output, $ok);
+
+        if ($ok > 0) {
+            $io->error(join("\n", array_merge([$cmd], $output)));
+            return Command::FAILURE;
+        }
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Checks/Compatibility.php b/cli/Commands/Checks/Compatibility.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e6cd3ee1e0e4385f4fd26b8b459c963d3eb8053
--- /dev/null
+++ b/cli/Commands/Checks/Compatibility.php
@@ -0,0 +1,167 @@
+<?php
+namespace Studip\Cli\Commands\Checks;
+
+use DirectoryIterator;
+use FilesystemIterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RecursiveRegexIterator;
+use RegexIterator;
+use Studip\Cli\Commands\AbstractCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Compatibility extends AbstractCommand
+{
+    protected static $defaultName = 'check:compatibility';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Compatibility scanner');
+        $this->setHelp('Scans plugins for common issues (backward compatibility and the like)');
+
+        $this->addArgument(
+            'version',
+            InputArgument::OPTIONAL,
+            'Version to check against (if not suppied, all checks are performed)'
+        );
+
+        $this->addArgument(
+            'folder',
+            InputArgument::IS_ARRAY,
+            'Folder to scan (will default to the plugins_packages folder)'
+        );
+
+        $this->addOption('filenames', 'f', InputOption::VALUE_NONE, 'Display filenames only');
+        $this->addOption(
+            'recursive',
+            'r',
+            InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE,
+            'Do not scan recursively into subfolders'
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $output->getFormatter()->setStyle('issue', new OutputFormatterStyle('red'));
+        $output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold']));
+
+        $rules = $this->getCompatibilityRules($input->getArgument('version'));
+        $folders = $input->getArgument('folder') ?: $this->getDefaultFolders();
+        $recursive = $input->getOption('recursive') ?? true;
+
+        foreach ($folders as $f) {
+            $folder = $this->validateFolder($f);
+            if (!$folder) {
+                $output->writeln("<info>Skipping invalid folder {$f}</info>", OutputInterface::VERBOSITY_VERBOSE);
+
+                continue;
+            }
+
+            $issues = [];
+            foreach ($this->getFolderIterator($folder, $recursive, ['php', 'tpl', 'inc', 'js']) as $file) {
+                $filename = $file->getPathName();
+                $output->writeln("<info>Checking {$filename}", OutputInterface::VERBOSITY_VERBOSE);
+                if ($errors = $this->checkFilecontentsAgainstRules($filename, $rules)) {
+                    $issues[$filename] = $errors;
+                }
+            }
+
+            if (count($issues) === 0) {
+                continue;
+            }
+
+            if (!$input->getOption('filenames')) {
+                $issue_count = array_sum(array_map('count', $issues));
+                $message = count($issues) === 1
+                    ? '%u issue found in <bold>%s</bold>'
+                    : '%u issues found in <bold>%s</bold>';
+
+                $output->writeln(sprintf(
+                    "<issue>{$message}</issue>",
+                    $issue_count,
+                    $this->relativeFilePath($folder)
+                ));
+            }
+
+            foreach ($issues as $filename => $errors) {
+                if ($input->getOption('filenames')) {
+                    $output->writeln($filename);
+                } else {
+                    $output->writeln(sprintf(
+                        '> File <fg=green;options=bold>%s</>',
+                        $this->relativeFilePath($filename)
+                    ));
+                    foreach ($errors as $needle => $suggestion) {
+                        $output->writeln(
+                            sprintf('- <fg=cyan>%s</> -> %s', $needle, $suggestion ?: '<fg=red>No suggestion available')
+                        );
+                    }
+                }
+            }
+        }
+
+        return Command::SUCCESS;
+    }
+
+    private function getCompatibilityRules(?string $version): array
+    {
+        if ($version !== null) {
+            if (!file_exists(__DIR__ . "/compatibility-rules/studip-{$version}.php")) {
+                throw new \Exception("No rules defined for Stud.IP version {$version}");
+            }
+
+            return require __DIR__ . "/compatibility-rules/studip-{$version}.php";
+        }
+
+        $rules = [];
+        foreach (glob(__DIR__ . '/compatbility-rules/*.php') as $file) {
+            $version_rules = require $file;
+            $rules = array_merge($rules, $version_rules);
+        }
+
+        return $rules;
+    }
+
+    private function getDefaultFolders(): array
+    {
+        $folders = rtrim($GLOBALS['STUDIP_BASE_PATH'], '/') . '/public/plugins_packages';
+        $folders = glob($folders . '/*/*');
+        return $folders;
+    }
+
+    private function validateFolder(string $folder)
+    {
+        if (!file_exists($folder) || !is_dir($folder)) {
+            return false;
+        }
+
+        return $folder;
+    }
+
+    private function checkFilecontentsAgainstRules(string $filename, array $rules)
+    {
+        $errors = [];
+
+        $contents = strtolower(file_get_contents($filename));
+        foreach ($rules as $needle => $suggestion) {
+            if ($this->checkRule($contents, $needle)) {
+                $errors[$needle] = $suggestion;
+            }
+        }
+        return $errors;
+    }
+
+    private function checkRule(string $contents, string $rule)
+    {
+        if ($rule[0] === '/' && $rule[strlen($rule) - 1] === '/') {
+            return (bool) preg_match("{$rule}s", $contents);
+        }
+
+        return strpos($contents, strtolower($rule)) > 0;
+    }
+}
diff --git a/cli/Commands/Checks/GlobalizedConfig.php b/cli/Commands/Checks/GlobalizedConfig.php
new file mode 100644
index 0000000000000000000000000000000000000000..373534a8b2aa17f12666b18fea3c2b77b2f9fabc
--- /dev/null
+++ b/cli/Commands/Checks/GlobalizedConfig.php
@@ -0,0 +1,148 @@
+<?php
+namespace Studip\Cli\Commands\Checks;
+
+use Config;
+use DirectoryIterator;
+use FilesystemIterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RecursiveRegexIterator;
+use RegexIterator;
+use Studip\Cli\Commands\AbstractCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+final class GlobalizedConfig extends AbstractCommand
+{
+    protected static $defaultName = 'check:globalized-config';
+
+    protected function configure(): void
+    {
+        $this->setDescription(
+            '<href=https://develop.studip.de/trac/ticket/5671>TIC 5671</> scanner - Globalized config'
+        );
+        $this->setHelp(
+            'Scans files for occurences of globalized config items (see <href=https://develop.studip.de/trac/ticket/5671>ticket 5671</> for more info)'
+        );
+
+        $this->addOption('filenames', 'f', InputOption::VALUE_NONE, 'Display filenames only (excludes -m and -o)');
+        $this->addOption('matches', 'm', InputOption::VALUE_NONE, 'Show matched config variables');
+        $this->addOption(
+            'recursive',
+            'r',
+            InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE,
+            'Do not scan recursively into subfolders'
+        );
+        $this->addOption('occurences', 'o', InputOption::VALUE_NONE, 'Show occurences in files');
+
+        $this->addArgument(
+            'folder',
+            InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
+            'Folder(s) to scan (pass the special value of "plugins" to scan the plugin folder)',
+            [$GLOBALS['STUDIP_BASE_PATH']]
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $only_filenames = (bool) $input->getOption('filenames');
+        $show_occurences = !$only_filenames && ($output->isVerbose() || $input->getOption('occurences'));
+        $show_matches = !$only_filenames && ($show_occurences || $input->getOption('matches'));
+        $recursive = $input->getOption('recursive') ?? true;
+
+        $folders = $input->getArgument('folder');
+        foreach ($folders as $index => $folder) {
+            if ($folder === 'plugins') {
+                $folders[$index] = $GLOBALS['STUDIP_BASE_PATH'] . '/public/plugins_packages/';
+            }
+        }
+        $folders = array_unique($folders);
+
+        $config = Config::get()->getFields('global');
+        $quoted = array_map(function ($item) {
+            return preg_quote($item, '/');
+        }, $config);
+        $regexp = '/\$(?:GLOBALS\[["\']?)?(' . implode('|', $quoted) . ')\b/S';
+
+        foreach ($folders as $folder) {
+            if (!file_exists($folder) || !is_dir($folder)) {
+                $output->writeln(
+                    "Skipping non-folder argument <fg=red>{$folder}</>",
+                    OutputInterface::VERBOSITY_VERBOSE
+                );
+                continue;
+            }
+            $output->writeln("Scanning {$folder}", OutputInterface::VERBOSITY_VERBOSE);
+
+            foreach ($this->getFolderIterator($folder, $recursive, ['php', 'tpl', 'inc']) as $file) {
+                $filename = $file->getPathName();
+                $contents = file_get_contents($filename);
+
+                $output->writeln(
+                    sprintf(
+                        'Check <fg=magenta>%s</>',
+                        $this->relativeFilePath($filename)
+                    ),
+                    OutputInterface::VERBOSITY_VERBOSE
+                );
+
+                if ($matched = preg_match_all($regexp, $contents, $matches)) {
+                    if ($only_filenames) {
+                        $output->writeln($filename);
+                    } else {
+                        $output->writeln(
+                            sprintf(
+                                '%u matched variable(s) in <fg=green;options=bold>%s</>',
+                                $matched,
+                                $this->shorten($filename)
+                            )
+                        );
+                        if ($show_matches) {
+                            $variables = array_unique($matches[1]);
+                            foreach ($variables as $variable) {
+                                $output->writeln("> <fg=cyan>{$variable}</>");
+                                if ($show_occurences) {
+                                    $output->writeln($this->highlight($contents, $variable));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return Command::SUCCESS;
+    }
+
+    private function highlight(string $content, string $variable): string
+    {
+        $lines = explode("\n", $content);
+
+        $result = [];
+        foreach ($lines as $index => $line) {
+            if (mb_strpos($line, $variable) === false) {
+                continue;
+            }
+            $result[$index + 1] = $line;
+        }
+
+        if (!$result) {
+            return '';
+        }
+
+        $max = max(array_map('mb_strlen', array_keys($result)));
+
+        foreach ($result as $index => $line) {
+            $result[$index] = sprintf(
+                "<fg=yellow>:%0{$max}u:</> %s",
+                $index,
+                str_replace($variable, "<fg=black;bg=yellow>{$variable}</>", $line)
+            );
+        }
+
+        return implode("\n", $result);
+    }
+}
diff --git a/cli/Commands/Checks/HelpTours.php b/cli/Commands/Checks/HelpTours.php
new file mode 100644
index 0000000000000000000000000000000000000000..04d66c0335efbff9cd59467ad83f4f00d6b8a168
--- /dev/null
+++ b/cli/Commands/Checks/HelpTours.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Studip\Cli\Commands\Checks;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class HelpTours extends Command
+{
+    protected static $defaultName = 'check:helptours';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Checks help tours for validity.');
+        $this->setHelp('This command will check all active help tours if the sites used are still available');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+
+        foreach (\HelpTour::findBySQL('1 ORDER BY name ASC') as $tour) {
+            if (!$tour->settings->active) {
+                if ($output->isVerbose()) {
+                    $tour_name = $this->getTourName($tour);
+                    $io->info("Skipping inactive tour {$tour_name}");
+                }
+
+                continue;
+            }
+
+            $errors = [];
+            foreach ($tour->steps->orderBy('step ASC') as $step) {
+                try {
+                    if (match_route('plugins.php/*', $step->route)) {
+                        $result = \PluginEngine::routeRequest(substr($step->route, strlen('plugins.php') + 1));
+
+                        // retrieve corresponding plugin info
+                        $plugin_manager = \PluginManager::getInstance();
+                        $plugin_info = $plugin_manager->getPluginInfo($result[0]);
+
+                        $file = implode('/', [
+                            \Config::get()->PLUGINS_PATH,
+                            $plugin_info['path'],
+                            $plugin_info['class'],
+                        ]);
+
+                        if (file_exists($file . '.php')) {
+                            $file .= '.php';
+                        } elseif (file_exists($file . '.class.php')) {
+                            $file .= '.class.php';
+                        } else {
+                            throw new \Exception();
+                        }
+                        require_once $file;
+                        $plugin = new $plugin_info['class']();
+
+                        if ($result[1]) {
+                            $dispatcher = new \Trails_Dispatcher(
+                                $GLOBALS['ABSOLUTE_PATH_STUDIP'] . $plugin->getPluginPath(),
+                                rtrim(\PluginEngine::getLink($plugin, [], null, true), '/'),
+                                'index'
+                            );
+                            $dispatcher->current_plugin = $plugin;
+                            $parsed = $dispatcher->parse($result[1]);
+                            $controller = $dispatcher->load_controller($parsed[0]);
+                            if ($parsed[1] && !$controller->has_action($parsed[1])) {
+                                throw new \Exception();
+                            }
+                        }
+                    } elseif (match_route('dispatch.php/*', $step->route)) {
+                        $dispatcher = new \StudipDispatcher();
+                        $parsed = $dispatcher->parse(substr($step->route, strlen('dispatch.php') + 1));
+                        $controller = $dispatcher->load_controller($parsed[0]);
+                        if ($parsed[1] && !$controller->has_action($parsed[1])) {
+                            throw new \Exception();
+                        }
+                    } elseif (!file_exists("{$GLOBALS['ABSOLUTE_PATH_STUDIP']}{$step->route}")) {
+                        throw new \Exception();
+                    }
+                } catch (\Exception $e) {
+                    $errors[$step->step] = $step->route;
+                }
+            }
+
+            if ($errors) {
+                $tour_name = $this->getTourName($tour);
+                $io->error("{$tour_name} has errors in the following steps:");
+
+                $io->table(
+                    ['Step', 'Route'],
+                    array_map(
+                        function ($step, $route) {
+                            return [$step, $route];
+                        },
+                        array_keys($errors),
+                        array_values($errors)
+                    )
+                );
+            }
+        }
+
+        return Command::SUCCESS;
+    }
+
+    private function getTourName(\HelpTour $tour)
+    {
+        $type = ucfirst($tour->type);
+        return "{$type} '{$tour->name}' ({$tour->language})";
+    }
+}
diff --git a/cli/Commands/Checks/compatibility-rules/studip-4.0.php b/cli/Commands/Checks/compatibility-rules/studip-4.0.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b3fa22f6481b5f2652c9a284a96108bc22cc704
--- /dev/null
+++ b/cli/Commands/Checks/compatibility-rules/studip-4.0.php
@@ -0,0 +1,178 @@
+<?php
+// "Rules"/definitions for critical changes in 4.0
+return [
+    'cssClassSwitcher' => 'Remove completely, use <fg=yellow><table class="default"></> instead.',
+    '$csssw' => '[<fg=cyan>cssClassSwitcher</>] Remove completely, use <fg=yellow><table class="default"></> instead.',
+
+    'DBMigration' => 'Use <fg=yellow>Migration</> instead',
+
+    'Request::removeMagicQuotes()' => 'Remove completely since magic quotes are removed from php',
+
+    'base_without_infobox' => 'Use <fg=yellow>layouts/base.php</> instead.',
+    'deprecated_tabs_layout' => 'Don\'t use this. Use the global layout <fg=yellow>layouts/base.php</> and <fg=yellow>Navigation</> instead.',
+    'setInfoBoxImage' => 'Replace with <fg=yellow>Sidebar</>',
+    'addToInfobox'    => 'Replace with <fg=yellow>Sidebar</>',
+    'InfoboxElement'  => 'Replace with appropriate <fg=yellow>Sidebar</> element',
+    'InfoboxWidget'   => 'Replace with appropriate <fg=yellow>Sidebar</> widget',
+
+    'details.php' => 'Link to <fg=yellow>dispatch.php/course/details</> instead',
+    'institut_main.php' => 'Link to <fg=yellow>dispatch.php/institute/overview</> instead',
+    'meine_seminare.php' => 'Link to <fg=yellow>dispatch.php/my_courses</> instead',
+    'sms_box.php' => 'Link to <fg=yellow>dispatch.php/messages/overview</> or <fg=yellow>dispatch.php/messages/sent</>  instead',
+    'sms_send.php' => 'Link to <fg=yellow>dispatch.php/messages/write</> instead',
+
+    'get_global_perm' => 'Use <fg=yellow>$GLOBALS[\'perm\']->get_perm()</> instead',
+    'log_event(' => 'Use <fg=yellow>StudipLog::log()</> instead',
+    '->removeOutRangedSingleDates' => 'Use <fg=yellow>SeminarCycleDate::removeOutRangedSingleDates</> instead',
+
+    'HolidayData' => 'Use class <fg=yellow>SemesterHoliday</> instead',
+
+    'CourseTopic::createFolder' => 'Use <fg=yellow>CourseTopic::connectWithDocumentFolder()</> instead',
+    'SimpleORMap::haveData' => 'Use <fg=yellow>SimpleORMap::isDirty()</> or <fg=yellow>SimpleORMap::isNew()</> instead',
+    'Seminar::getMetaDateType' => 'Don\'t use this!',
+    'UserConfig::setUserId' => 'Don\'t use this. <fg=yellow>Set the user via the constructor</>.',
+
+    'StudIPTemplateEngine' => 'Time to refactor your plugin.',
+    'AbstractStudIPAdministrationPlugin' => 'Time to refactor your plugin.',
+    'AbstractStudIPCorePlugin' => 'Time to refactor your plugin.',
+    'AbstractStudIPHomepagePlugin' => 'Time to refactor your plugin.',
+    'AbstractStudIPLegacyPlugin' => 'Time to refactor your plugin.',
+    'AbstractStudIPPortalPlugin' => 'Time to refactor your plugin.',
+    'AbstractStudIPStandardPlugin' => 'Time to refactor your plugin.',
+    'AbstractStudIPSystemPlugin' => 'Time to refactor your plugin.',
+    'new Permission(' => 'Time to refactor your plugin.',
+    'Permission::' => 'Time to refactor your plugin.',
+    'PluginNavigation' => 'Time to refactor your plugin.',
+    'new StudIPUser(' => 'Time to refactor your plugin.',
+    'StudIPUser::' => 'Time to refactor your plugin.',
+    'StudipPluginNavigation' => 'Time to refactor your plugin.',
+    'getLinkToAdministrationPlugin' => 'Time to refactor your plugin.',
+    'getCurrentPluginId' => 'Time to refactor your plugin.',
+    'saveToSession' => 'Time to refactor your plugin.',
+    'getValueFromSession' => 'Time to refactor your plugin.',
+
+    'ContainerTable'   => false,
+    'DbCrossTableView' => false,
+    'DbPermissions'    => false,
+
+    'pclzip' => 'Use <fg=yellow>Studip\\ZipArchive</> instead',
+
+    'getSeminarRoomRequest' => 'Use <fg=yellow>RoomRequest</> model instead',
+    'getDateRoomRequest' => 'Use <fg=yellow>RoomRequest</> model instead',
+
+    'ldate' => 'Use PHP\'s <fg=yellow>date()</> or <fg=yellow>strftime()</> function instead',
+    'day_diff' => 'Use PHP\'s <fg=yellow>DateTime::diff()</> method instead',
+    'get_day_name' => 'Use PHP\'s <fg=yellow>strftime()</> function with <fg=yellow>parameter \'%A\'</> instead',
+    'wday(' => 'Use <fg=yellow>strftime("%a")</> or <fg=yellow>strftime("%A")</> instead',
+
+    'get_ampel_state' => false,
+    'get_ampel_write' => false,
+    'get_ampel_read' => false,
+    'localePictureUrl' => false,
+    'localeUrl' => false,
+    'isDatesMultiSem' => false,
+    'getMetadateCorrespondingDates' => false,
+    'getCorrespondingMetadates' => false,
+    'create_year_view' => false,
+    'javascript_hover_year' => false,
+    'js_hover' => false,
+    'info_icons' => false,
+
+    'get_message_attachments' => 'Use <fg=yellow>Message::attachments</> attribute instead',
+    'view_turnus' => 'Use <fg=yellow>Seminar::getFormattedTurnus()</> instead',
+
+    'AddNewStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'CheckSelfAssign' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'CheckSelfAssignAll' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'CheckAssignRights' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'SetSelfAssignAll' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'SetSelfAssignExclusive' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'EditStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'MovePersonPosition' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'SortPersonInAfter' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'SortStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'SubSortStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'resortStatusgruppeByRangeId' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'SwapStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'CheckStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'GetRangeOfStatusgruppe' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'GetGroupsByCourseAndUser' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'getOptionsOfStGroups' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'setOptionsOfStGroup' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'GetStatusgruppeLimit' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'CheckStatusgruppeFolder' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'CheckStatusgruppeMultipleAssigns' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'sortStatusgruppeByName' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'getPersons(' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'getSearchResults(' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+    'setExternDefaultForUser' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+
+    'GetStatusgruppeName' => 'Use <fg=yellow>Statusgruppen::find($id)->name</> instead',
+    'GetStatusgruppenForUser' => 'Use class <fg=yellow>Statusgruppe</> or model <fg=yellow>Statusgruppen</> instead (yupp, this is still pretty fucked up).',
+
+    'get_global_visibility_by_id' => 'Use <fg=yellow>User::find($id)->visible</> instead',
+    'get_global_visibility_by_username' => 'Use <fg=yellow>User::findByUsername($username)->visible</> instead',
+
+    'get_local_visibility_by_username' => false,
+    'get_homepage_element_visibility' => false,
+    'set_homepage_element_visibility' => false,
+    'checkVisibility' => 'Use <fg=yellow>Visibility::verify($param, $this->current_user->user_id)</> instead',
+
+    'InsertPersonStatusgruppe' => 'Use <fg=Statusgruppen>:addUser()</> instead',
+    'RemovePersonStatusgruppe(' => 'Use <fg=yellow>Statusgruppen::find($group_id)->removeUser($user_id)</> instead',
+    'RemovePersonStatusgruppeComplete' => 'Use <fg=yellow>Statusgruppen::find($group_id)->removeUser($user_id, true)</> instead. Maybe you will need to do this on a collection of groups for a course or institute.',
+    'RemovePersonFromAllStatusgruppen' => 'Use <fg=yellow>StatusgruppeUser::deleteBySQL("user_id = ?", [$user_id])</> instead.',
+    'DeleteAllStatusgruppen' => 'Use <fg=yellow>Statusgruppen::deleteBySQL("range_id = ?", [$id]);</> instead',
+    'DeleteStatusgruppe' => 'Use <fg=yellow>Statusgruppen::delete()</> - or <fg=yellow>Statusgruppen::remove()</> if you want to keep the child groups.',
+    'moveStatusgruppe' => false,
+    'CheckUserStatusgruppe' => 'Use <fg=yellow>StatusgruppeUser::exists([$group_id, $user_id])</> instead.',
+    'CountMembersStatusgruppen' => false,
+    'CountMembersPerStatusgruppe' => false,
+    'MakeDatafieldsDefault' => 'No longer neccessary.',
+    'MakeUniqueStatusgruppeID' => 'No longer neccessary. SORM will create ids for you.',
+    'GetAllSelected' => 'Use <fg=yellow>Statusgruppen::findAllByRangeId()</> instead.',
+    'getStatusgruppenIDS' => 'Use <fg=yellow>Statusgruppen::findByRange_id()</> instead.',
+    'getAllStatusgruppenIDS' => 'Use <fg=yellow>Statusgruppen::findAllByRangeId()</> instead.',
+    'getPersonsForRole' => 'Use <fg=yellow>:Statusgruppen::members</> instead.',
+    'isVatherDaughterRelation' => false,
+    'SetSelfAssign(' => false,
+    'getExternDefaultForUser' => 'Use <fg=yellow>InstituteMember::getDefaultInstituteIdForUser($user_id)</> instead.',
+    'checkExternDefaultForUser' => 'Use <fg=yellow>InstituteMember::ensureDefaultInstituteIdForUser($user_id)</> instead.',
+    'getAllChildIDs' => false,
+    'getKingsInformations' => 'Use <fg=yellow>User</> model instead',
+
+    'AutoInsert::existSeminars' => false,
+    'new ZebraTable' => 'No longer neccessary. Use <fg=yellow>table.default</> instead.',
+    'new Table' => 'No longer neccessary. Use <fg=yellow>table.default</> instead.',
+
+    //old datei.inc.php and visual.inc.php functions:
+    'createSelectedZip' => 'Removed. Use <fg=yellow>FileArchiveManager::createArchiveFromFileRefs</> instead.',
+    'create_zip_from_directory' => 'Removed(?). Use <fg=yellow>FileArchiveManager::createArchiveFromPhysicalFolder</> instead.',
+    'getFileExtension' => 'Removed. Use PHP\'s built-in <fg=yellow>pathinfo($filename, PATHINFO_EXTENSION)</> instead.',
+    'get_icon_for_mimetype' => 'Removed. Use <fg=yellow>FileManager::getIconNameForMimeType</> instead.',
+    'get_upload_file_path' => 'Removed. Use <fg=yellow>File->getPath()</> instead.',
+    'GetDownloadLink' => 'Removed. Use one of the following alternatives instead: <fg=yellow>FileRef->getDownloadURL()</>, <fg=yellow>FileManager::getDownloadLinkForArchivedCourse</>, <fg=yellow>FileManager::getDownloadLinkForTemporaryFile</> or <fg=yellow>FileManager::getDownloadURLForTemporaryFile</>',
+    'prepareFilename' => 'Removed. Use <fg=yellow>FileManager::cleanFileName</> instead.',
+    'GetFileIcon' => 'Removed. Use <fg=yellow>FileManager::getIconNameForMimeType</> instead.',
+    'parse_link' => 'Removed. Use <fg=yellow>FileManager::fetchURLMetadata</> instead.',
+    'unzip_file' => 'Removed. Use <fg=yellow>Studip\ZipArchive::extractToPath</> or <fg=yellow>Studip\ZipArchive::test</> instead.',
+    'datei.inc.php' => 'Removed. Use methods in functions.inc.php, FileManager, FileArchiveManager, FileRef, File or FolderType instead.',
+    'TrackAccess' => 'Removed(?). Use <fg=yellow>:FileRef::incrementDownloadCounter</>',
+    //StudipDocument and related classes:
+    'StudipDocument(' => 'Removed(?). Use class <fg=yellow>FileRef</> instead.',
+    'DocumentFolder(' => 'Removed(?). Use class <fg=yellow>Folder</> instead.',
+    'StudipDocumentTree(' => 'Removed(?). Use class <fg=yellow>Folder</> or <fg=yellow>FolderType</> instead.',
+    'WysiwygDocument' => 'Deprecated/To be removed. Use class <fg=yellow>FileRef</> in conjunction with a <fg=yellow>FolderType</> implementation instead.',
+
+    'ZIP_USE_INTERNAL' => 'Removed. Please avoid querying the value of this configuration variable!',
+    'ZIP_PATH' => 'Removed. Please avoid querying the value of this configuration variable!',
+    'ZIP_OPTIONS' => 'Removed. Please avoid querying the value of this configuration variable!',
+    'UNZIP_PATH' => 'Removed. Please avoid querying the value of this configuration variable!',
+
+    'RuleAdministrationModel::getAdmissionRuleTypes' => 'Use <fg=yellow>AdmissionRule::getAvailableAdmissionRules(false)</> instead.',
+    'SessSemName' => 'Use class <fg=yellow>Context</> instead',
+    '_SESSION["SessionSeminar"]' => 'Use class <fg=yellow>Context</> instead',
+    '_SESSION[\'SessionSeminar\']' => 'Use class <fg=yellow>Context</> instead',
+
+    'Statusgruppe(' => 'Removed(?). Use class <fg=yellow>Statusgruppen</> instead.',
+];
diff --git a/cli/Commands/Checks/compatibility-rules/studip-4.2.php b/cli/Commands/Checks/compatibility-rules/studip-4.2.php
new file mode 100644
index 0000000000000000000000000000000000000000..c051d7eb2c95bbcb8862f663afb0360defe30703
--- /dev/null
+++ b/cli/Commands/Checks/compatibility-rules/studip-4.2.php
@@ -0,0 +1,17 @@
+<?php
+// "Rules"/definitions for critical changes in 4.2
+return [
+    'get_perm' => 'Use the <fg=yellow>CourseMember</> or <fg=yellow>InstitutMember</> model instead.',
+    'get_vorname' => 'Use <fg=yellow>User::find($id)->vorname</> instead',
+    'get_nachname' => 'Use <fg=yellow>User::find($id)->nachname</> instead',
+    'get_range_tree_path' => false,
+    'get_seminar_dozent' => 'Use <fg=yellow>Course::find($id)->getMembersWithStatus(\'dozent\')</> instead.',
+    'get_seminar_tutor' => 'Use <fg=yellow>Course::find($id)->getMembersWithStatus(\'tutor\')</> instead.',
+    'get_seminar_sem_tree_entries' => false,
+    'get_seminars_users' => 'Use <fg=yellow>CourseMember::findByUser($user_id)</> instead to aquire all courses.',
+    'remove_magic_quotes' => false,
+    'text_excerpt' => false,
+    'check_group_new' => false,
+    'insertNewSemester' => 'Use the <fg=yellow>Semester</> model instead.',
+    'updateExistingSemester' => 'Use the <fg=yellow>Semester</> model instead.',
+];
diff --git a/cli/Commands/Checks/compatibility-rules/studip-4.4.php b/cli/Commands/Checks/compatibility-rules/studip-4.4.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa4f326bb536242e76e4cde5d47ff94020b13dbe
--- /dev/null
+++ b/cli/Commands/Checks/compatibility-rules/studip-4.4.php
@@ -0,0 +1,6 @@
+<?php
+// "Rules"/definitions for critical changes in 4.4
+return [
+    'Token::is_valid' => 'Use <fg=yellow>Token::isValid($token, $user_id)</> instead.',
+    'Token::generate' => 'Use <fg=yellow>Token::create($duration = 30, $user_id = null)</> instead.',
+];
diff --git a/cli/Commands/Checks/compatibility-rules/studip-5.0.php b/cli/Commands/Checks/compatibility-rules/studip-5.0.php
new file mode 100644
index 0000000000000000000000000000000000000000..5aec2492037b03ec053fa9870f0aeace939a44e5
--- /dev/null
+++ b/cli/Commands/Checks/compatibility-rules/studip-5.0.php
@@ -0,0 +1,60 @@
+<?php
+// "Rules"/definitions for critical changes in 5.0
+return [
+    // https://develop.studip.de/trac/ticket/11250
+    'userMayAccessRange' => '<fg=yellow>Changed</> - Use <fg=yellow>isAccessibleToUser</> instead',
+    'userMayEditRange' => '<fg=yellow>Changed</> - Use <fg=yellow>isEditableByUser</> instead',
+    'userMayAdministerRange' => '<fg=red>Removed</>',
+
+    // UTF8-Encode/Decode legacy functions
+    'studip_utf8encode' => '<fg=red>Removed</> - Use utf8_encode().',
+    'studip_utf8decode' => '<fg=red>Removed</> - Use utf8_decode().',
+
+    // JSON encode/decode legacy functions
+    'studip_json_decode' => '<fg=red>Deprecated</> - Use json_decode() and pay attention to the second parameter.',
+    'studip_json_encode' => '<fg=red>Deprecated</> - Use json_encode().',
+
+    // https://develop.studip.de/trac/ticket/10806
+    'SemesterData' => '<fg=red>Removed</> - Use <fg=yellow>Semester model</> instead',
+
+    // https://develop.studip.de/trac/ticket/10786
+    'StatusgroupsModel' => '<fg=red>Removed</> - Use <fg=yellow>Statusgruppen model</> instead',
+
+    // https://develop.studip.de/trac/ticket/10796
+    'StudipNullCache' => '<fg=red>Removed</> - Use <fg=yellow>StudipMemoryCache</> instead',
+
+    // https://develop.studip.de/trac/ticket/10838
+    'getDeputies' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::findDeputies()</> instead',
+    'getDeputyBosses' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::findDeputyBosses()</> instead',
+    '/(?<!Deputy::)addDeputy/' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::addDeputy()</> instead',
+    '/deleteDeputy(?=\()/' => '<fg=red>Removed</> - Use <fg=yellow>Deputy model</> instead',
+    'deleteAllDeputies' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::deleteByRange_id</> instead',
+    '/(?<!Deputy::)isDeputy/' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::isDeputy()</> instead',
+    'setDeputyHomepageRights' => '<fg=red>Removed</> - Use <fg=yellow>Deputy model</> instead',
+    'getValidDeputyPerms' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::getValidPerms()</> instead',
+    'isDefaultDeputyActivated' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::isActivated()</> instead',
+    'getMyDeputySeminarsQuery' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::getMySeminarsQuery()</> instead',
+    'isDeputyEditAboutActivated' => '<fg=red>Removed</> - Use <fg=yellow>Deputy::isEditActivated()</> instead',
+
+    // https://develop.studip.de/trac/ticket/10870
+    'get_config' => '<fg=red>Deprecated</> - Use <fg=yellow>Config::get()</> instead.',
+
+    // https://develop.studip.de/trac/ticket/10919
+    'RESTAPI\\RouteMap' => '<fg=red>Deprecated</> - Use the <fg=yellow>JSONAPI</> instead.',
+
+    // https://develop.studip.de/trac/ticket/10878
+    'Leafo\\ScssPhp' => 'Library was replaced by <fg=yellow>scssphp/scssphp</>',
+    'sfYamlParser'   => 'Library was replaced by <fg=yellow>symfony/yaml</>',
+    'DocBlock::of'   => 'Library was replaced by <fg=yellow>gossi/docblock</>',
+
+    'vendor/idna_convert' => 'Remove include/require. Will be autoloaded.',
+    'vendor/php-htmldiff' => 'Remove include/require. Will be autoloaded.',
+    'vendor/HTMLPurifier' => 'Remove include/require. Will be autoloaded.',
+    'vendor/phplot'       => 'Remove include/require. Will be autoloaded.',
+    'vendor/phpCAS'       => 'Remove include/require. Will be autoloaded.',
+    'vendor/phpxmlrpc'    => 'Remove include/require. Will be autoloaded.',
+
+    // https://develop.studip.de/trac/ticket/10964
+    'periodicalPushData'                           => '<fg=red>Removed</> - Use <fg=yellow>STUDIP.JSUpdater.register()</> instead',
+    '/UpdateInformtion::setInformation\(.+\..+\)/' => '<fg=red>Removed</> - Use <fg=yellow>STUDIP.JSUpdater.register()</> instead',
+];
diff --git a/cli/Commands/CleanupAdmissionRules.php b/cli/Commands/CleanupAdmissionRules.php
new file mode 100644
index 0000000000000000000000000000000000000000..3079ae4c3ecad5462bdbecbdd7f6a0ef89ca7dde
--- /dev/null
+++ b/cli/Commands/CleanupAdmissionRules.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Studip\Cli\Commands;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CleanupAdmissionRules extends Command
+{
+    protected static $defaultName = 'cleanup:admission-rules';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Cleanup admission-rules.');
+        $this->setHelp('Deletes entries in %admissions tables.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        require_once 'lib/classes/admission/CourseSet.class.php';
+
+        $course_set = new \CourseSet();
+
+        $sql = "SELECT * FROM
+(
+SELECT rule_id,'ConditionalAdmission' as class FROM `conditionaladmissions`
+UNION
+SELECT rule_id,'CourseMemberAdmission' as class FROM `coursememberadmissions`
+UNION
+SELECT rule_id,'LimitedAdmission' as class FROM limitedadmissions
+UNION
+SELECT rule_id,'LockedAdmission' as class FROM lockedadmissions
+UNION
+SELECT rule_id,'ParticipantRestrictedAdmission' as class FROM participantrestrictedadmissions
+UNION
+SELECT rule_id,'PasswordAdmission' as class FROM passwordadmissions
+UNION
+SELECT rule_id,'TimedAdmission' as class FROM timedadmissions
+) a
+LEFT JOIN courseset_rule USING(rule_id) WHERE set_id IS NULL";
+
+        $c1 = $c2 = 0;
+        \DBManager::get()->fetchAll($sql, null, function ($data) use (&$c1, &$c2, $output) {
+            $c1++;
+            $class_name = '\\' . $data['class'];
+            if (class_exists($class_name)) {
+                $rule = new $class_name($data['rule_id']);
+                if ($rule->getId() === $data['rule_id']) {
+                    $output->writeln(sprintf('deleting: %s with id: %s', $rule->getName(), $rule->getId()));
+                    $c2++;
+                    $rule->delete();
+                }
+            }
+        });
+        $output->writeln(sprintf('found: %s deleted: %s', $c1, $c2));
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Cronjobs/CronjobExecute.php b/cli/Commands/Cronjobs/CronjobExecute.php
new file mode 100644
index 0000000000000000000000000000000000000000..23592a1bac9af9decf84b55c97a0697984744182
--- /dev/null
+++ b/cli/Commands/Cronjobs/CronjobExecute.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Studip\Cli\Commands\Cronjobs;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CronjobExecute extends Command
+{
+    protected static $defaultName = 'cronjobs:execute';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Execute cronjob task.');
+        $this->setHelp('This command will execute a cronjob task.');
+        $this->addArgument('task_id', InputArgument::REQUIRED, 'Id of the desired cron job');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $task_id = $input->getArgument('task_id');
+        $task = \CronjobTask::find($task_id);
+        if (!$task) {
+            $output->writeln('<error>Unknown task id</error>');
+            return Command::FAILURE;
+        }
+        if (!file_exists($GLOBALS['STUDIP_BASE_PATH'] . '/' . $task->filename)) {
+            $output->writeln(sprintf('<error>Invalid task, unknown filename %s</error>', $task->filename));
+            return Command::FAILURE;
+        }
+        require_once $GLOBALS['STUDIP_BASE_PATH'] . '/' . $task->filename;
+        if (!class_exists('\\' . $task->class)) {
+            fwrite(STDOUT, 'Invalid task, unknown class "' . $task->class . '"' . PHP_EOL);
+            $output->writeln(sprintf('<error>Invalid task, unknown class %s</error>', $task->class));
+            return Command::FAILURE;
+        }
+        $task->engage('');
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Cronjobs/CronjobList.php b/cli/Commands/Cronjobs/CronjobList.php
new file mode 100644
index 0000000000000000000000000000000000000000..af4913d582f1e714b1b10aa08148bde90085afa0
--- /dev/null
+++ b/cli/Commands/Cronjobs/CronjobList.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Studip\Cli\Commands\Cronjobs;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Helper\TableSeparator;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CronjobList extends Command
+{
+    protected static $defaultName = 'cronjobs:list';
+
+    protected function configure(): void
+    {
+        $this->setDescription('List cronjobs.');
+        $this->setHelp('This command lists all available cronjobs.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $tasks = \CronjobTask::findBySql('1');
+
+        if ($tasks) {
+            $table = new Table($output);
+            $table->setStyle('compact');
+            $table->setHeaders(['Task-ID', 'Description']);
+            foreach ($tasks as $task) {
+                $description = call_user_func(['\\' . $task->class, 'getDescription']);
+                if ($description) {
+                    $table->addRow([$task->id, $description]);
+                }
+            }
+            $table->render();
+        }
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Cronjobs/CronjobWorker.php b/cli/Commands/Cronjobs/CronjobWorker.php
new file mode 100644
index 0000000000000000000000000000000000000000..209f11286bc0834a8a23cb69b7895688fda5295d
--- /dev/null
+++ b/cli/Commands/Cronjobs/CronjobWorker.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Studip\Cli\Commands\Cronjobs;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CronjobWorker extends Command
+{
+    protected static $defaultName = 'cronjobs:worker';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Cronjob worker.');
+        $this->setHelp('Worker process for the cronjobs.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        \CronjobScheduler::getInstance()->run();
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/DB/Dump.php b/cli/Commands/DB/Dump.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2a8a2f8a27213b4be4ee19c304276195a55d0bf
--- /dev/null
+++ b/cli/Commands/DB/Dump.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Studip\Cli\Commands\DB;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class Dump extends Command
+{
+    protected static $defaultName = 'db:dump';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Dump the given database schema');
+        $this->addArgument('path', InputArgument::REQUIRED, 'path where the backup should be saved');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $dump_dir = realpath($input->getArgument('path'));
+        $today = date('Ymd');
+        $prefix = \Config::get()->STUDIP_INSTALLATION_ID ? \Config::get()->STUDIP_INSTALLATION_ID : 'studip';
+
+        if (!is_writeable($dump_dir)) {
+            $io->error('Directory: ' . $dump_dir . ' is not writeable!');
+            return Command::FAILURE;
+        }
+        $dump_db_dir = $dump_dir . '/db-' . $today;
+        if (!\is_dir($dump_db_dir)) {
+            \mkdir($dump_db_dir);
+        }
+        $command = $this->getBaseDumpCommand();
+        $output = [];
+        $result_code = 0;
+
+        foreach (\DBManager::get()->query('SHOW TABLES') as $tables) {
+            $table = $tables[0];
+
+            $dump_table = $dump_db_dir . '/' . $table . '-' . $today . '.sql';
+
+            $io->writeln('<info>Dumping database table ' . $table . '</info>');
+
+            $cmd = $command . ' ' . $table . ' > ' . $dump_table;
+            $this->runCommand($cmd, $output, $result_code);
+
+            if ($result_code > 0) {
+                $io->error($this->parseOutput($cmd, $output));
+                return Command::FAILURE;
+            }
+        }
+        $dump_db = $dump_dir . '/' . $prefix . '-DB-' . $today . '.tar.gz';
+
+        $io->writeln('<info>Packing database to ' . $dump_db . '</info>');
+
+        $cmd = "cd $dump_db_dir && tar -czf $dump_db *";
+        $this->runCommand($cmd, $output, $result_code);
+
+        if ($result_code > 0) {
+            $io->error($this->parseOutput($cmd, $output));
+            return Command::FAILURE;
+        }
+
+        $cmd = "rm -rf $dump_db_dir";
+        $this->runCommand($cmd, $output, $result_code);
+
+        if ($result_code > 0) {
+            $io->error($this->parseOutput($cmd, $output));
+            return Command::FAILURE;
+        }
+
+        return Command::SUCCESS;
+    }
+
+    /**
+     * Get the base dump command arguments for MySQL as a string.
+     *
+     * @return string
+     */
+    private function getBaseDumpCommand(): string
+    {
+        $command =
+            'mysqldump ' .
+            $this->getConnectionString() .
+            ' --skip-add-locks --skip-comments --skip-set-charset --tz-utc';
+
+        if (!\DBManager::get()->isMariaDB()) {
+            $command .= ' --column-statistics=0 --set-gtid-purged=OFF';
+        }
+        return $command . ' ' . $GLOBALS['DB_STUDIP_DATABASE'];
+    }
+
+    /**
+     * Generate a basic connection string (--socket, --host, --port, --user, --password) for the database.
+     *
+     * @return string
+     */
+    private function getConnectionString(): string
+    {
+        return ' --user="' .
+            $GLOBALS['DB_STUDIP_USER'] .
+            '"  --password="' .
+            $GLOBALS['DB_STUDIP_PASSWORD'] .
+            '"  --host="' .
+            $GLOBALS['DB_STUDIP_HOST'] .
+            '"';
+    }
+
+    /**
+     * Execute dump command
+     * @param string $cmd
+     * @param array $output
+     * @param int $result_code
+     */
+    private function runCommand(string $cmd, array &$output, int &$result_code)
+    {
+        exec($cmd . ' 2>&1', $output, $result_code);
+    }
+
+    /**
+     * Parse output of exec()
+     * @param string $cmd
+     * @param array $output
+     * @return string
+     */
+    private function parseOutput(string &$cmd, array &$output): string
+    {
+        $result = join('\n', array_merge([$cmd], $output));
+        $cmd = '';
+        $output = [];
+        return $result;
+    }
+}
diff --git a/cli/Commands/DB/MigrateEngine.php b/cli/Commands/DB/MigrateEngine.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ae5219e29a7c46687b99ee8545f29fc7dae3609
--- /dev/null
+++ b/cli/Commands/DB/MigrateEngine.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Studip\Cli\Commands\DB;
+
+use DBManager;
+use StudipPDO;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class MigrateEngine extends Command
+{
+    protected static $defaultName = 'db:migrate-engine';
+
+    protected function configure(): void
+    {
+        $this->setDescription('MyISAM to InnoDB');
+        $this->setHelp('Migrate the Engine from MyISAM to InnoDB.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $io->info('Migration starting at ' . date('d.m.Y H:i:s'));
+        $start = microtime(true);
+        // Check if InnoDB is enabled in database server.
+        $engines = DBManager::get()->fetchAll('SHOW ENGINES');
+        $innodb = false;
+        foreach ($engines as $e) {
+            // InnoDB is found and enabled.
+            if ($e['Engine'] == 'InnoDB' && in_array(mb_strtolower($e['Support']), ['default', 'yes'])) {
+                $innodb = true;
+                break;
+            }
+        }
+        if ($innodb) {
+            // Get version of database system (MySQL/MariaDB/Percona)
+            $data = DBManager::get()->fetchFirst('SELECT VERSION() AS version');
+            $version = $data[0];
+
+            // Tables to ignore on engine conversion.
+            $ignore_tables = [];
+            // Fetch all tables that need to be converted.
+            $tables = DBManager::get()->fetchFirst(
+                "SELECT TABLE_NAME
+        FROM `information_schema`.TABLES
+        WHERE TABLE_SCHEMA=:database AND ENGINE=:oldengine
+        ORDER BY TABLE_NAME",
+                [
+                    ':database' => $GLOBALS['DB_STUDIP_DATABASE'],
+                    ':oldengine' => 'MyISAM',
+                ]
+            );
+
+            /*
+             * lit_catalog needs fulltext indices which InnoDB doesn't support
+             * in older versions.
+             */
+            if (version_compare($version, '5.6', '<')) {
+                $stmt_fulltext = DBManager::get()->prepare(
+                    "SHOW INDEX FROM :database.:table WHERE Index_type = 'FULLTEXT'"
+                );
+                foreach ($tables as $k => $t) {
+                    $stmt_fulltext->bindParam(':table', $t, StudipPDO::PARAM_COLUMN);
+                    $stmt_fulltext->bindParam(':database', $DB_STUDIP_DATABASE, StudipPDO::PARAM_COLUMN);
+                    $stmt_fulltext->execute();
+                    if ($stmt_fulltext->fetch()) {
+                        $ignore_tables[] = $t;
+                        unset($tables[$k]);
+                    }
+                }
+                if (count($ignore_tables)) {
+                    $io->info(
+                        'The following tables needs fulltext indices ' .
+                            'which are not supported for InnoDB in your database ' .
+                            'version, so the tables will be left untouched: ' .
+                            join(',', $ignore_tables)
+                    );
+                }
+            }
+
+            // Use Barracuda format if database supports it (5.5 upwards).
+            if (version_compare($version, '5.5', '>=')) {
+                $io->info('Found MySQL in version >= 5.5, checking if Barracuda file format is supported...');
+                // Get innodb_file_per_table setting
+                $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_per_table'");
+
+                $file_per_table = $data['Value'];
+
+                // Check if Barracuda file format is enabled
+                $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_format'");
+                $file_format = $data['Value'];
+
+                if (mb_strtolower($file_per_table) == 'on' && mb_strtolower($file_format) == 'barracuda') {
+                    $rowformat = 'DYNAMIC';
+                } else {
+                    if (mb_strtolower($file_per_table) != 'on') {
+                        $io->info('file_per_table not set');
+                    }
+                    if (mb_strtolower($file_format) != 'barracuda') {
+                        $io->info('file_format not set to Barracuda (but to ' . $file_format . ')');
+                    }
+                    $rowformat = 'COMPACT';
+                }
+            }
+
+            // Prepare query for table conversion.
+            $stmt = DBManager::get()->prepare('ALTER TABLE :database.:table ROW_FORMAT=:rowformat ENGINE=:newengine');
+            $stmt->bindParam(':database', $DB_STUDIP_DATABASE, StudipPDO::PARAM_COLUMN);
+            $stmt->bindParam(':rowformat', $rowformat, StudipPDO::PARAM_COLUMN);
+            $newengine = 'InnoDB';
+            $stmt->bindParam(':newengine', $newengine, StudipPDO::PARAM_COLUMN);
+
+            // Now convert the found tables.
+            foreach ($tables as $t) {
+                $local_start = microtime(true);
+                $stmt->bindParam(':table', $t, StudipPDO::PARAM_COLUMN);
+                $stmt->execute();
+                $local_end = microtime(true);
+                $local_duration = $local_end - $local_start;
+                $human_local_duration = sprintf(
+                    '%02d:%02d:%02d',
+                    ($local_duration / 60 / 60) % 24,
+                    ($local_duration / 60) % 60,
+                    $local_duration % 60
+                );
+                $io->info('Conversion of table ' . $t . ' took ' . $human_local_duration);
+            }
+
+            $end = microtime(true);
+
+            $duration = $end - $start;
+            $human_duration = sprintf(
+                '%02d:%02d:%02d',
+                ($duration / 60 / 60) % 24,
+                ($duration / 60) % 60,
+                $duration % 60
+            );
+
+            $io->info('Migration finished at ' . date('d.m.Y H:i:s') . ', duration ' . $human_duration);
+        } else {
+            $io->error(
+                'The storage engine InnoDB is not enabled in your database installation, tables cannot be converted.'
+            );
+        }
+    }
+}
diff --git a/cli/Commands/DB/MigrateFileFormat.php b/cli/Commands/DB/MigrateFileFormat.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c97824797a6d22c4e0fbb8dcdbc1e451bca8413
--- /dev/null
+++ b/cli/Commands/DB/MigrateFileFormat.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Studip\Cli\Commands\DB;
+
+use DBManager;
+use StudipPDO;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class MigrateFileFormat extends Command
+{
+    protected static $defaultName = 'db:migrate-file-format';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Antelope to Barracuda');
+        $this->setHelp('Migrate the FileFormat from Antelope to Barracuda.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $io->info('Migration starting at ' . date('d.m.Y H:i:s'));
+        $start = microtime(true);
+        // Check if InnoDB is enabled in database server.
+        $engines = DBManager::get()->fetchAll('SHOW ENGINES');
+        $innodb = false;
+        foreach ($engines as $e) {
+            // InnoDB is found and enabled.
+            if ($e['Engine'] == 'InnoDB' && in_array(mb_strtolower($e['Support']), ['default', 'yes'])) {
+                $innodb = true;
+                break;
+            }
+        }
+        if ($innodb) {
+            // Get version of database system (MySQL/MariaDB/Percona)
+            $data = DBManager::get()->fetchFirst('SELECT VERSION() AS version');
+            $version = $data[0];
+
+            // Use Barracuda format if database supports it (5.5 upwards).
+            if (version_compare($version, '5.5', '>=')) {
+                $io->info('Checking if Barracuda file format is supported...');
+                // Get innodb_file_per_table setting
+                $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_per_table'");
+                $file_per_table = $data['Value'];
+
+                // Check if Barracuda file format is enabled
+                $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_format'");
+                $file_format = $data['Value'];
+                if (mb_strtolower($file_per_table) == 'on' && mb_strtolower($file_format) == 'barracuda') {
+                    // Fetch all tables that need to be converted.
+                    $tables = DBManager::get()->fetchFirst(
+                        "SELECT TABLE_NAME
+                FROM `information_schema`.TABLES
+                WHERE TABLE_SCHEMA=:database AND ENGINE=:engine
+                    AND ROW_FORMAT IN (:rowformats)
+                ORDER BY TABLE_NAME",
+                        [
+                            ':database' => $GLOBALS['DB_STUDIP_DATABASE'],
+                            ':engine' => 'InnoDB',
+                            ':rowformats' => ['Compact', 'Redundant'],
+                        ]
+                    );
+
+                    $newformat = 'DYNAMIC';
+
+                    // Prepare query for table conversion.
+                    $stmt = DBManager::get()->prepare('ALTER TABLE :database.:table ROW_FORMAT=:newformat');
+                    $stmt->bindParam(':database', $DB_STUDIP_DATABASE, StudipPDO::PARAM_COLUMN);
+                    $stmt->bindParam(':newformat', $newformat, StudipPDO::PARAM_COLUMN);
+
+                    if (count($tables) > 0) {
+                        // Now convert the found tables.
+                        foreach ($tables as $t) {
+                            $local_start = microtime(true);
+                            $stmt->bindParam(':table', $t, StudipPDO::PARAM_COLUMN);
+                            $stmt->execute();
+                            $local_end = microtime(true);
+                            $local_duration = $local_end - $local_start;
+                            $human_local_duration = sprintf(
+                                '%02d:%02d:%02d',
+                                ($local_duration / 60 / 60) % 24,
+                                ($local_duration / 60) % 60,
+                                $local_duration % 60
+                            );
+                            $io->info('Converserion of table' . $t . ' took ' . $human_local_duration);
+                        }
+                    } else {
+                        $io->error('No Antelope format tables found');
+                        return Command::FAILURE;
+                    }
+                } else {
+                    if (mb_strtolower($file_per_table) != 'on') {
+                        $io->error('file_per_table not set');
+                        return Command::FAILURE;
+                    }
+                    if (mb_strtolower($file_format) != 'barracuda') {
+                        $io->error('file_format not set to Barracuda (but to ' . $file_format . ')');
+                        return Command::FAILURE;
+                    }
+                }
+                $end = microtime(true);
+                $duration = $end - $start;
+                $human_duration = sprintf(
+                    '%02d:%02d:%02d',
+                    ($duration / 60 / 60) % 24,
+                    ($duration / 60) % 60,
+                    $duration % 60
+                );
+
+                $io->info('Migration finished at ' . date('d.m.Y H:i:s') . ', duration ' . $human_duration);
+                return Command::SUCCESS;
+            } else {
+                $io->error(
+                    'Your database server does not yet support the Barracuda row format (you need at least 5.5)'
+                );
+                return Command::FAILURE;
+            }
+        } else {
+            $io->error(
+                'The storage engine InnoDB is not enabled in your database installation, tables cannot be converted.'
+            );
+            return Command::INVALID;
+        }
+    }
+}
diff --git a/cli/Commands/Files/Dump.php b/cli/Commands/Files/Dump.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d5c48c472c444dfa7903b3d834c83902e6584e8
--- /dev/null
+++ b/cli/Commands/Files/Dump.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Studip\Cli\Commands\Files;
+
+use Config;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class Dump extends Command
+{
+    protected static $defaultName = 'files:dump';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Dumping data directory');
+        $this->addArgument('path', InputArgument::REQUIRED, 'path where the backup should be saved');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $dump_dir = realpath($input->getArgument('path'));
+        $today = date('Ymd');
+        $prefix = Config::get()->STUDIP_INSTALLATION_ID ? Config::get()->STUDIP_INSTALLATION_ID : 'studip';
+
+        $data_path = realpath($GLOBALS['UPLOAD_PATH'] . '/../');
+
+        if (!$data_path) {
+            $io->error('Stud.IP upload folder not found!');
+            return Command::FAILURE;
+        }
+
+        $dumb_data = $dump_dir . '/' . $prefix . '-DATA-' . $today . '.tar.gz';
+
+        $io->info('Dumping data directory to ' . $dumb_data);
+
+        $cmd = "cd $data_path && tar -czf $dumb_data ." . ' 2>&1';
+
+        exec($cmd, $output, $ok);
+
+        if ($ok > 0) {
+            $io->error(join("\n", array_merge([$cmd], $output)));
+            return Command::FAILURE;
+        }
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Fix/Biest7789.php b/cli/Commands/Fix/Biest7789.php
new file mode 100644
index 0000000000000000000000000000000000000000..43f0c680e0eb294d50172979d841d6bdebed9c23
--- /dev/null
+++ b/cli/Commands/Fix/Biest7789.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Studip\Cli\Commands\Fix;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * For example
+ *
+ * php cli/studip fix:biest-7789 extern_config config
+ * php cli/studip fix:biest-7789 aux_lock_rules attributes
+ * php cli/studip fix:biest-7789 aux_lock_rules sorting
+ * php cli/studip fix:biest-7789 user_config value "field = 'MY_COURSES_ADMIN_VIEW_FILTER_ARGS'"
+ * php cli/studip fix:biest-7789 mail_queue_entries mail
+ */
+class Biest7789 extends Command
+{
+    protected static $defaultName = 'fix:biest-7789';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Fix Biest #7789');
+        $this->addArgument('table', InputArgument::REQUIRED, 'database table');
+        $this->addArgument('column', InputArgument::REQUIRED, 'table column');
+        $this->addArgument('where', InputArgument::OPTIONAL, 'where clause');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        ini_set('default_charset', 'utf-8');
+        $io = new SymfonyStyle($input, $output);
+        $table = $input->getArgument('table');
+        $column = $input->getArgument('column');
+        $where = $input->getArgument('where');
+
+        $db = \DBManager::get();
+
+        $io->title($table);
+        // get primary keys
+        $result = $db->query("SHOW KEYS FROM $table WHERE Key_name = 'PRIMARY'");
+        $keys = [];
+
+        while ($data = $result->fetch(\PDO::FETCH_ASSOC)) {
+            $keys[] = $data['Column_name'];
+        }
+
+        // retrieve and convert data
+        $result = $db->query(
+            'SELECT `' . implode('`,`', $keys) . "`, `$column` FROM `$table` WHERE " . ($where ?: '1')
+        );
+
+        while ($data = $result->fetch(\PDO::FETCH_ASSOC)) {
+            $content = unserialize(\legacy_studip_utf8decode($data[$column]));
+
+            if ($content === false) {
+                // try to fix string length denotations
+                $fixed = preg_replace_callback(
+                    '/s:([0-9]+):\"(.*?)\";/s',
+                    function ($matches) {
+                        return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';
+                    },
+                    $data[$column]
+                );
+
+                $content = unserialize(\legacy_studip_utf8decode($fixed));
+            }
+
+            if ($content !== false) {
+                // encode all data
+                $json = json_encode($this->legacy_studip_utf8encode($content), true);
+
+                $query = "UPDATE `$table` SET `$column` = " . $db->quote($json) . "\n WHERE ";
+
+                $where_query = [];
+                foreach ($keys as $key) {
+                    $where_query[] = "`$key` = " . $db->quote($data[$key]);
+                }
+
+                $q = $query . implode(' AND ', $where_query);
+                $db->exec($q);
+                $io->writeln("<info>$q</info>");
+            } else {
+                $io->writeln(sprintf('<error>Could not convert: %s</error>', print_r($data, 1)));
+            }
+        }
+        return Command::SUCCESS;
+    }
+
+    private function legacy_studip_utf8encode($data)
+    {
+        if (is_array($data)) {
+            $new_data = [];
+            foreach ($data as $key => $value) {
+                $key = $this->legacy_studip_utf8encode($key);
+                $new_data[$key] = $this->legacy_studip_utf8encode($value);
+            }
+            return $new_data;
+        }
+
+        if (!\preg_match('/[\200-\377]/', $data) && !\preg_match("'&#[0-9]+;'", $data)) {
+            return $data;
+        } else {
+            return \mb_decode_numericentity(
+                \mb_convert_encoding($data, 'UTF-8', 'WINDOWS-1252'),
+                [0x100, 0xffff, 0, 0xffff],
+                'UTF-8'
+            );
+        }
+    }
+}
diff --git a/cli/Commands/Fix/Biest7866.php b/cli/Commands/Fix/Biest7866.php
new file mode 100644
index 0000000000000000000000000000000000000000..591e79f469d7b56d41ddc6b318b98aa48ee1272d
--- /dev/null
+++ b/cli/Commands/Fix/Biest7866.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Studip\Cli\Commands\Fix;
+
+use DBManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class Biest7866 extends Command
+{
+    protected static $defaultName = 'fix:biest-7866';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Fix Biest #7866');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $root_folders = DBManager::get()->fetchAll("SELECT `id`, `range_id` FROM `folders` WHERE `parent_id` = ''");
+
+        foreach ($root_folders as $r) {
+            $this->setFolderRangeId($io, $r['id'], $r['range_id']);
+        }
+        return Command::SUCCESS;
+    }
+
+    /**
+     * Sets the range_id of all child folders to the given range_id.
+     * @param SymfonyStyle $io
+     * @param string $parent_folder
+     * @param string $range_id
+     */
+    private function setFolderRangeId(SymfonyStyle $io, string $parent_folder, string $range_id)
+    {
+        // Update all child folder range_ids.
+        DBManager::get()->execute('UPDATE `folders` SET `range_id` = :range WHERE `parent_id` = :parent', [
+            'range' => $range_id,
+            'parent' => $parent_folder,
+        ]);
+
+        // Recursion: set correct range_id for child folders with wrong range_id.
+        $children = DBManager::get()->fetchAll('SELECT `id`, `range_id` FROM `folders` WHERE `parent_id` = :parent', [
+            'parent' => $parent_folder,
+        ]);
+        foreach ($children as $child) {
+            if ($child['range_id'] != $range_id) {
+                $io->info(sprintf("Folder %s -> range_id %s.\n", $child['id'], $range_id));
+            }
+            $this->setFolderRangeId($io, $child['id'], $range_id);
+        }
+    }
+}
diff --git a/cli/Commands/Fix/Biest8136.php b/cli/Commands/Fix/Biest8136.php
new file mode 100644
index 0000000000000000000000000000000000000000..17fe2490f159e31f853073351c8ba99280bb1f22
--- /dev/null
+++ b/cli/Commands/Fix/Biest8136.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Studip\Cli\Commands\Fix;
+
+use DBManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class Biest8136 extends Command
+{
+    protected static $defaultName = 'fix:biest-8136';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Fix Biest #8136');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+
+        $query = "UPDATE `activities`
+          SET `actor_type` = 'anonymous',
+              `actor_id` = ''
+          WHERE `provider` = :provider
+            AND `actor_type` != 'anonymous'
+            AND `object_id` IN (
+                SELECT `topic_id`
+                FROM `forum_entries`
+                WHERE `anonymous` != 0
+            )";
+        $statement = DBManager::get()->prepare($query);
+        $statement->bindValue(':provider', 'Studip\\Activity\\ForumProvider');
+        $statement->execute();
+
+        $io->info(sprintf('%u forum post activities were anonymized', $statement->rowCount()));
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php b/cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php
new file mode 100644
index 0000000000000000000000000000000000000000..40e7a2b0e0290d92c27c162c70bd83ebd0b0b489
--- /dev/null
+++ b/cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Studip\Cli\Commands\Fix;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class EndTimeWeeklyRecurredEvents extends Command
+{
+    protected static $defaultName = 'fix:end-time-weekly-recurred-events';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Fix end time weekly recurred events');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $events = \EventData::findBySQL("rtype = 'WEEKLY' AND IFNULL(count, 0) > 0");
+        $cal_event = new \CalendarEvent();
+
+        $i = 0;
+
+        foreach ($events as $event) {
+            $id = $event->getId();
+            $cal_event->event = $event;
+            $rrule = $cal_event->getRecurrence();
+            $cal_event->setRecurrence($rrule);
+            $event->expire = $cal_event->event->expire;
+            $event->setId($id);
+            $event->store();
+            $i++;
+        }
+        $io->info('Wrong end time of recurrence fixed for ' . $i . ' events.');
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Fix/IconDimensions.php b/cli/Commands/Fix/IconDimensions.php
new file mode 100644
index 0000000000000000000000000000000000000000..13703e9ac91af94a9238e457770f4a54b0908fcd
--- /dev/null
+++ b/cli/Commands/Fix/IconDimensions.php
@@ -0,0 +1,49 @@
+<?php
+namespace Studip\Cli\Commands\Fix;
+
+use FilesystemIterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RecursiveRegexIterator;
+use RegexIterator;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class IconDimensions extends Command
+{
+    protected static $defaultName = 'fix:icon-dimensions';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Fix icon dimensions in their svg files');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $folder = $GLOBALS['STUDIP_BASE_PATH'] . '/public/assets/images/icons';
+        $iterator = new RecursiveDirectoryIterator(
+            $folder,
+            FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS
+        );
+        $iterator = new RecursiveIteratorIterator($iterator);
+        $regexp_iterator = new RegexIterator($iterator, '/\.svg$/', RecursiveRegexIterator::MATCH);
+
+        foreach ($regexp_iterator as $file) {
+            $contents = file_get_contents($file);
+
+            $xml = simplexml_load_string($contents);
+            $attr = $xml->attributes();
+            if ($attr->width && $attr->height) {
+                continue;
+            }
+
+            $contents = str_replace('<svg ', '<svg width="16" height="16" ', $contents);
+            file_put_contents($file, $contents);
+
+            $output->writeln("Adjusted {$file}");
+        }
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/HelpContent/Migrate.php b/cli/Commands/HelpContent/Migrate.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ab790a00f1c55aa30be1953dbd947682d6093da
--- /dev/null
+++ b/cli/Commands/HelpContent/Migrate.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Studip\Cli\Commands\HelpContent;
+
+use DBManager;
+use PDO;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class Migrate extends Command
+{
+    protected static $defaultName = 'help-content:migrate';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Migrate help-content.');
+        $this->addArgument('version', InputArgument::REQUIRED, 'Version of the help content');
+        $this->addArgument('language', InputArgument::REQUIRED, 'Language of the help content');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $io = new SymfonyStyle($input, $output);
+        $version = $input->getArgument('version');
+        $language = $input->getArgument('language');
+        $help_content_path = $GLOBALS['STUDIP_BASE_PATH'] . '/doc/helpbar';
+
+        $query = 'SELECT * FROM help_content WHERE studip_version = ? LIMIT 1';
+        $statement = DBManager::get()->prepare($query);
+        $statement->execute([$version]);
+        $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC);
+
+        if ($ret && count($ret)) {
+            $io->info('Helpbar content already present for this version!');
+            return Command::SUCCESS;
+        }
+
+        $filename = $help_content_path . '/' . $language . '/helpcontent.json';
+
+        if (!is_file($filename)) {
+            $io->error('File not found: ' . $filename);
+            return Command::FAILURE;
+        }
+
+        $json = json_decode(file_get_contents($filename), true);
+
+        if ($json === null) {
+            $io->error('Helpbar content could not be loaded. File: ' . $filename);
+            return Command::FAILURE;
+        }
+
+        $count = [];
+        foreach ($json as $row) {
+            if (!is_array($row['text'])) {
+                $row['text'] = [$row['text']];
+            }
+            if (!$row['label'] || !$row['icon']) {
+                $row['label'] = '';
+            }
+            $installation_id = \Config::get()->STUDIP_INSTALLATION_ID;
+            foreach ($row['text'] as $index => $text) {
+                $count[$language . $row['route']]++;
+                $statement = DBManager::get()->prepare(
+                    "INSERT INTO help_content (
+                          `content_id`,
+                          `language`,
+                          `label`,
+                          `icon`,
+                          `content`,
+                          `route`,
+                          `studip_version`,
+                          `position`,
+                          `custom`,
+                          `visible`,
+                          `author_id`,
+                          `installation_id`,
+                          `mkdate`
+                      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 1, '', ?, UNIX_TIMESTAMP())"
+                );
+                $statement->execute([
+                    md5(uniqid(rand(), true)),
+                    $language,
+                    $index == 0 ? $row['label'] : '',
+                    $index == 0 ? $row['icon'] : '',
+                    $text,
+                    $row['route'],
+                    $version,
+                    $count[$language . $row['route']],
+                    $installation_id,
+                ]);
+            }
+        }
+
+        if (count($count)) {
+            if (!\Config::get()->getValue('HELP_CONTENT_CURRENT_VERSION')) {
+                \Config::get()->create('HELP_CONTENT_CURRENT_VERSION', [
+                    'value' => $version,
+                    'is_default' => 0,
+                    'type' => 'string',
+                    'range' => 'global',
+                    'section' => 'global',
+                    'description' => _('Aktuelle Version der Helpbar-Einträge in Stud.IP'),
+                ]);
+            } else {
+                \Config::get()->store('HELP_CONTENT_CURRENT_VERSION', $version);
+            }
+        }
+        $io->success('help content added for ' . count($count) . ' routes.');
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Migrate/Migrate.php b/cli/Commands/Migrate/Migrate.php
new file mode 100644
index 0000000000000000000000000000000000000000..f5acff89dac9e0bc8ae07e7ba87de4feea407e5a
--- /dev/null
+++ b/cli/Commands/Migrate/Migrate.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Studip\Cli\Commands\Migrate;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Migrate extends Command
+{
+    protected static $defaultName = 'migrate';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Run the database migrations.');
+        $this->setHelp('This command runs all pending database migrations.');
+
+        $this->addOption('branch', 'b', InputOption::VALUE_OPTIONAL, 'the branch of the migrations', '0');
+
+        $this->addOption('domain', 'd', InputOption::VALUE_OPTIONAL, 'the domain of the migrations', 'studip');
+
+        $defaultPath = $GLOBALS['STUDIP_BASE_PATH'] . '/db/migrations';
+        $this->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'the id of the migration to list to', $defaultPath);
+
+        $this->addOption('target', 't', InputOption::VALUE_OPTIONAL, 'the target version', null);
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $branch = $input->getOption('branch');
+        $domain = $input->getOption('domain');
+        $path = $input->getOption('path');
+        $target = $input->getOption('target');
+        $verbose = $input->getOption('verbose');
+
+        $version = new \DBSchemaVersion($domain, $branch);
+        $migrator = new \Migrator($path, $version, $verbose);
+        $migrator->migrateTo($target);
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Migrate/MigrateList.php b/cli/Commands/Migrate/MigrateList.php
new file mode 100644
index 0000000000000000000000000000000000000000..cff6ce190d4886a86307b8d60ab7a67a8e3d7b50
--- /dev/null
+++ b/cli/Commands/Migrate/MigrateList.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Studip\Cli\Commands\Migrate;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class MigrateList extends Command
+{
+    protected static $defaultName = 'migrate:list';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Shows all pending migrations.');
+        $this->setHelp('This command shows a list of all pending migrations.');
+
+        $this->addOption('branch', 'b', InputOption::VALUE_OPTIONAL, 'the branch of the migrations', '0');
+
+        $this->addOption('domain', 'd', InputOption::VALUE_OPTIONAL, 'the domain of the migrations', 'studip');
+
+        $defaultPath = $GLOBALS['STUDIP_BASE_PATH'] . '/db/migrations';
+        $this->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'the path to the migrations', $defaultPath);
+
+        $this->addOption('target', 't', InputOption::VALUE_OPTIONAL, 'the target version', null);
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $branch = $input->getOption('branch');
+        $domain = $input->getOption('domain');
+        $path = $input->getOption('path');
+        $target = $input->getOption('target');
+        $verbose = $input->getOption('verbose');
+
+        $version = new \DBSchemaVersion($domain, $branch);
+        $migrator = new \Migrator($path, $version, $verbose);
+
+        $migrationClasses = $migrator->migrationClasses();
+        $migrations = $migrator->relevantMigrations($target);
+        if (count($migrations)) {
+            $data = [];
+            foreach ($migrations as $number => $migration) {
+                $description = $migration->description() ?: '(no description)';
+                $name = basename($migrationClasses[$number][0], '.php');
+                $data[] = [$number, $name, $description];
+            }
+
+            $table = new Table($output);
+            $table->setHeaders(['ID', 'Migration', 'Description'])->setRows($data);
+            $table->setStyle('box');
+            $table->render();
+        }
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Migrate/MigrateStatus.php b/cli/Commands/Migrate/MigrateStatus.php
new file mode 100644
index 0000000000000000000000000000000000000000..e07bbe18bf21d26413efa91a2b313d217d18a690
--- /dev/null
+++ b/cli/Commands/Migrate/MigrateStatus.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Studip\Cli\Commands\Migrate;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class MigrateStatus extends Command
+{
+    protected static $defaultName = 'migrate:status';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Shows the state of all migrations.');
+        $this->setHelp('This command shows the state of all migrations.');
+
+        $this->addOption('domain', 'd', InputOption::VALUE_OPTIONAL, 'the domain of the migrations', 'studip');
+
+        $defaultPath = $GLOBALS['STUDIP_BASE_PATH'] . '/db/migrations';
+        $this->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'the id of the migration to list to', $defaultPath);
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $domain = $input->getOption('domain');
+        $path = $input->getOption('path');
+        $verbose = $input->getOption('verbose');
+
+        $version = new \DBSchemaVersion($domain);
+        $migrator = new \Migrator($path, $version, $verbose);
+
+        $migrations = $migrator->migrationClasses();
+        uksort($migrations, 'version_compare');
+        $migrations = array_reverse($migrations, true);
+
+        $pending = $migrator->relevantMigrations(null);
+
+        $rows = [];
+        foreach ($migrations as $number => $migration) {
+            $rows[] = [isset($pending[$number]) ? 'No' : 'Yes', $number, basename($migration[0], '.php')];
+        }
+
+        $table = new Table($output);
+        $table->setHeaders(['Ran?', 'ID', 'Migration'])->setRows($rows);
+        $table->setStyle('box');
+        $table->render();
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/I18N/I18NCommand.php b/cli/Commands/Plugins/I18N/I18NCommand.php
new file mode 100644
index 0000000000000000000000000000000000000000..6fa9f77c9cab3807509e3e14b6085ac48bcf9bad
--- /dev/null
+++ b/cli/Commands/Plugins/I18N/I18NCommand.php
@@ -0,0 +1,77 @@
+<?php
+namespace Studip\Cli\Commands\Plugins\I18N;
+
+use Exception;
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+
+abstract class I18NCommand extends AbstractPluginCommand
+{
+    private $plugin_manager = null;
+
+    protected function configure(): void
+    {
+        $this->addOption('pluginname', 'p', InputArgument::OPTIONAL, 'name of the plugin');
+        $this->addOption('folder', 'f', InputArgument::OPTIONAL, 'folder to scan (overrides pluginname)');
+    }
+
+    protected function getPluginFolder(InputInterface $input): string
+    {
+        $pluginname = $input->getOption('pluginname');
+        $folder     = $input->getOption('folder');
+
+        if (!$pluginname && !$folder) {
+            throw new Exception('You must specify either pluginname or folder.');
+        }
+
+        if (!$folder && $pluginname) {
+            $plugin = $this->findPluginByName($this->getPluginManager(), $pluginname);
+            if ($plugin === null) {
+                throw new Exception('Could not find plugin of that name.');
+            }
+
+            $folder = "{$GLOBALS['PLUGINS_PATH']}/{$plugin['path']}";
+        }
+
+        if (!$folder || !file_exists($folder) || !is_readable($folder)) {
+            throw new Exception('Could not access folder.');
+        }
+
+        return $folder;
+    }
+
+    protected function getPluginManifest(string $folder): array
+    {
+        return $this->getPluginManager()->getPluginManifest($folder);
+    }
+
+    protected function getPluginLocaleDomainByFolder(string $folder): string
+    {
+        $manifest = $this->getPluginManifest($folder);
+        if (!$manifest) {
+            throw new Exception("Could not detect plugin manifest in folder {$folder}");
+        }
+
+        if (!isset($manifest['localedomain'])) {
+            throw new Exception('Manifest has no defined localedomain');
+        }
+
+        return $manifest['localedomain'];
+    }
+
+    protected function getPluginManager(): \PluginManager
+    {
+        if ($this->plugin_manager === null) {
+            $this->plugin_manager = \PluginManager::getInstance();
+        }
+        return $this->plugin_manager;
+    }
+
+    protected function getSystemLanguages(): array
+    {
+        return array_map(function ($lang) {
+            return explode('_', $lang)[0];
+        }, array_keys($GLOBALS['INSTALLED_LANGUAGES']));
+    }
+}
diff --git a/cli/Commands/Plugins/I18N/I18NCompile.php b/cli/Commands/Plugins/I18N/I18NCompile.php
new file mode 100644
index 0000000000000000000000000000000000000000..178152e7d9c310395b0bdb1dd3203248aa6cf759
--- /dev/null
+++ b/cli/Commands/Plugins/I18N/I18NCompile.php
@@ -0,0 +1,57 @@
+<?php
+namespace Studip\Cli\Commands\Plugins\I18N;
+
+use Exception;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Process;
+
+final class I18NCompile extends I18NCommand
+{
+    protected static $defaultName = 'plugin:i18n:compile';
+
+    protected function configure(): void
+    {
+        parent::configure();
+
+        $this->setDescription('Compile translations');
+        $this->setHelp('This command compiles all .po files in the locale folder of the plugin.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        try {
+            $folder = $this->getPluginFolder($input);
+
+            foreach (glob("{$folder}/locale/*/LC_MESSAGES/*.po") as $po) {
+                $command_line = 'msgfmt "${:PO}" -o "${:MO}"';
+                $process = Process::fromShellCommandline($command_line);
+
+                try {
+                    $process->mustRun(null, [
+                        'PO' => $po,
+                        'MO' => preg_replace('/\.po$/', '.mo', $po),
+                    ]);
+
+                    $out = $process->getOutput();
+                    if ($out) {
+                        $output->writeln($out, OutputInterface::VERBOSITY_VERBOSE);
+                    }
+
+                    $output->writeln("Translations have been compiled successfully.");
+                } catch (ProcessFailedException $e) {
+                    $output->writeln("<error>Could not execute shell command</error>");
+                    $output->writeln($e->getmessage());
+                    return Command::FAILURE;
+                }
+            }
+
+            return Command::SUCCESS;
+        } catch (Exception $e) {
+            $output->writeln("<error>{$e->getMessage()}</error>");
+            return Command::FAILURE;
+        }
+    }
+}
diff --git a/cli/Commands/Plugins/I18N/I18NDetect.php b/cli/Commands/Plugins/I18N/I18NDetect.php
new file mode 100644
index 0000000000000000000000000000000000000000..584fe00500bdc664f1341d7c4d0edf56f5584fba
--- /dev/null
+++ b/cli/Commands/Plugins/I18N/I18NDetect.php
@@ -0,0 +1,59 @@
+<?php
+namespace Studip\Cli\Commands\Plugins\I18N;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+final class I18NDetect extends I18NCommand
+{
+    protected static $defaultName = 'plugin:i18n:detect';
+
+    protected function configure(): void
+    {
+        parent::configure();
+
+        $this->setDescription('Detect unmarked strings.');
+        $this->setHelp('This command detects probably unmarked strings for localization in php files.');
+        $this->addOption('only-filenames', '1', InputOption::VALUE_NONE, 'display only the filenames');
+        $this->addOption('absolute-filenames', 'a', InputOption::VALUE_NONE, 'display absolute filenames');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        try {
+            $folder = $this->getPluginFolder($input);
+        } catch (\Exception $e) {
+            $output->writeln("<error>{$e->getMessage()}</error>");
+            return Command::FAILURE;
+        }
+
+        $iterator = $this->getFolderIterator($folder, true, ['php']);
+
+        $found = false;
+        foreach ($iterator as $file) {
+            $filename = $file->getPathName();
+
+            $matched = preg_match('/(?<![$>])_\(/', file_get_contents($filename));
+            if ($matched) {
+                $output_filename = $input->getOption('absolute-filenames')
+                                 ? $filename
+                                 : $this->relativeFilePath($filename, true);
+
+                $message = $input->getOption('only-filenames')
+                         ? $output_filename
+                         : "<info>{$output_filename}</info>: {$matched} occurence(s)";
+
+                $output->writeln($message);
+                $found = true;
+            }
+        }
+
+        if (!$found) {
+            $output->writeln('<info>No unmarked translation strings found</info>');
+        }
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/I18N/I18NExtract.php b/cli/Commands/Plugins/I18N/I18NExtract.php
new file mode 100644
index 0000000000000000000000000000000000000000..e1f479fbe8e4a9ba95b207a91a1f4214852a579c
--- /dev/null
+++ b/cli/Commands/Plugins/I18N/I18NExtract.php
@@ -0,0 +1,74 @@
+<?php
+namespace Studip\Cli\Commands\Plugins\I18N;
+
+use Exception;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Process;
+
+final class I18NExtract extends I18NCommand
+{
+    protected static $defaultName = 'plugin:i18n:extract';
+
+    protected function configure(): void
+    {
+        parent::configure();
+
+        $this->setDescription('Extract localizable strings.');
+        $this->setHelp('This command extracts the localizable string from php files into a .pot file.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        try {
+            $folder   = $this->getPluginFolder($input);
+            $manifest = $this->getPluginManifest($folder);
+            $localedomain = $this->getPluginLocaleDomainByFolder($folder);
+
+            foreach ($this->getSystemLanguages() as $lang) {
+                $lang = explode('_', $lang)[0];
+                $language_dir = "{$folder}/locale/{$lang}/LC_MESSAGES";
+                if (!file_exists($language_dir)) {
+                    mkdir($language_dir, 0755, true);
+                }
+            }
+
+            $main_lang = $this->getSystemLanguages()[0];
+            $pot_file  = "{$folder}/locale/{$main_lang}/LC_MESSAGES/{$localedomain}.pot";
+            file_put_contents($pot_file, '');
+
+            $command_line = implode(' | ', [
+                'find "${:FOLDER}" -iname "*.php"',
+                'xargs xgettext --keyword=_n:1,2 --from-code=UTF-8 -j -n --language=PHP --add-location=never --package-name="${:PACKAGENAME}" -o "${:POTFILE}"',
+            ]);
+
+            $process = Process::fromShellCommandline($command_line);
+
+            try {
+                $process->mustRun(null, [
+                    'FOLDER'      => $folder,
+                    'PACKAGENAME' => $manifest['pluginclassname'],
+                    'POTFILE'     => $pot_file,
+                ]);
+
+                $out = $process->getOutput();
+                if ($out) {
+                    $output->writeln($out, OutputInterface::VERBOSITY_VERBOSE);
+                }
+
+                $output->writeln("Translation strings have been extracted successfully.");
+                return Command::SUCCESS;
+            } catch (ProcessFailedException $e) {
+                $output->writeln("<error>Could not execute shell command</error>");
+                $output->writeln($e->getmessage());
+                return Command::FAILURE;
+            }
+
+        } catch (Exception $e) {
+            $output->writeln("<error>{$e->getMessage()}</error>");
+            return Command::FAILURE;
+        }
+    }
+}
diff --git a/cli/Commands/Plugins/PluginActivate.php b/cli/Commands/Plugins/PluginActivate.php
new file mode 100644
index 0000000000000000000000000000000000000000..df5763593fab6dca53ff6c029523c5b9e456b738
--- /dev/null
+++ b/cli/Commands/Plugins/PluginActivate.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginActivate extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:activate';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Activate a plugin.');
+        $this->setHelp('This command activates a plugin.');
+        $this->addArgument('pluginname', InputArgument::REQUIRED, 'name of the plugin');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginname = $input->getArgument('pluginname');
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugin = $this->findPluginByName($pluginManager, $pluginname);
+        if (null === $plugin) {
+            $output->writeln('<error>Could not find plugin of that name.</error>');
+
+            return Command::FAILURE;
+        }
+
+        $pluginManager->setPluginEnabled($plugin['id'], true);
+        $output->writeln('Plugin activated.');
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginDeactivate.php b/cli/Commands/Plugins/PluginDeactivate.php
new file mode 100644
index 0000000000000000000000000000000000000000..637ad7f1e78147bc21a745a43d1e2199f49f725c
--- /dev/null
+++ b/cli/Commands/Plugins/PluginDeactivate.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginDeactivate extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:deactivate';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Deactivate a plugin.');
+        $this->setHelp('This command deactivates a plugin.');
+        $this->addArgument('pluginname', InputArgument::REQUIRED, 'name of the plugin');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginname = $input->getArgument('pluginname');
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugin = $this->findPluginByName($pluginManager, $pluginname);
+        if (null === $plugin) {
+            $output->writeln('<error>Could not find plugin of that name.</error>');
+
+            return Command::FAILURE;
+        }
+
+        $pluginManager->setPluginEnabled($plugin['id'], false);
+        $output->writeln('Plugin deactivated.');
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginInfo.php b/cli/Commands/Plugins/PluginInfo.php
new file mode 100644
index 0000000000000000000000000000000000000000..492c1aea14ea9a8688ab28207e7a3106baa3842d
--- /dev/null
+++ b/cli/Commands/Plugins/PluginInfo.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginInfo extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:info';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Shows information about matching plugins.');
+        $this->setHelp('This command shows information about plugins whose name contains the optional pattern.');
+        $this->addArgument('pattern', InputArgument::OPTIONAL, 'pattern to search for');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pattern = $input->getArgument('pattern');
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugins = $pluginManager->getPluginInfos();
+
+        if ($pattern) {
+            $plugins = array_filter($plugins, function ($plugin) use ($pattern) {
+                return false !== mb_stripos($plugin['name'], $pattern);
+            });
+        }
+
+        $basepath = \Config::get()->PLUGINS_PATH;
+        foreach ($plugins as $plugin) {
+            $plugindir = $basepath . '/' . $plugin['path'] . '/';
+
+            $plugin['class_exists'] = $this->pluginClassExists($plugindir, $plugin);
+            $plugin['type'] = join(',', $plugin['type']);
+
+            if (is_dir($plugindir . '/migrations')) {
+                $schemaVersion = new \DBSchemaVersion($plugin['name']);
+                $migrator = new \Migrator($plugindir . '/migrations', $schemaVersion);
+                $plugin['migration_top_version'] = $migrator->topVersion();
+                $plugin['schema_version'] = $schemaVersion->get();
+            }
+
+            $pairs = [];
+            foreach ($plugin as $key => $value) {
+                $pairs[] = [$key, $value];
+            }
+
+            $table = new Table($output);
+            $table->setHeaders(['Field', 'Value'])->setRows($pairs);
+            $table->setStyle('box');
+            $table->render();
+
+            $output->writeln('');
+        }
+
+        return Command::SUCCESS;
+    }
+
+    private function pluginClassExists(string $plugindir, array $plugin)
+    {
+        $pluginfile = $plugindir . $plugin['class'] . '.class.php';
+        if (file_exists($pluginfile)) {
+            return 1;
+        } else {
+            $pluginfile = $plugindir . $plugin['class'] . '.php';
+            if (file_exists($pluginfile)) {
+                return 1;
+            }
+        }
+
+        return 0;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginInstall.php b/cli/Commands/Plugins/PluginInstall.php
new file mode 100644
index 0000000000000000000000000000000000000000..1277f4f15d0fd73aefbe037f67fcb411f7f0254c
--- /dev/null
+++ b/cli/Commands/Plugins/PluginInstall.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginInstall extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:install';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Install a plugin.');
+        $this->setHelp('This command installs a plugin from a ZIP file.');
+        $this->addArgument('zipfile', InputArgument::REQUIRED, 'path to the ZIP file');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $zipfile = $input->getArgument('zipfile');
+
+        try {
+            $plugin_admin = new \PluginAdministration();
+            if (parse_url($zipfile, \PHP_URL_SCHEME)) {
+                $plugin_admin->installPluginFromURL($zipfile);
+            } else {
+                $plugin_admin->installPlugin($zipfile);
+            }
+            $output->writeln('The plugin was installed successfully.');
+        } catch (\PluginInstallationException $ex) {
+            $output->writeln('<error>' . $ex->getMessage() . '</error>');
+            return Command::FAILURE;
+        }
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginListMigrations.php b/cli/Commands/Plugins/PluginListMigrations.php
new file mode 100644
index 0000000000000000000000000000000000000000..2af092f88a71b6b2c470ab434cd3532459df6996
--- /dev/null
+++ b/cli/Commands/Plugins/PluginListMigrations.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginListMigrations extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:list-migrations';
+
+    protected function configure(): void
+    {
+        $this->setDescription('List all migrations of a plugin.');
+        $this->setHelp('This command lists all migrations of a plugin.');
+        $this->addArgument('pluginname', InputArgument::REQUIRED, 'name of the plugin');
+        $this->addOption('branch', 'b', InputOption::VALUE_OPTIONAL, 'branch of the migrations', '0');
+        $this->addOption('target', 't', InputOption::VALUE_OPTIONAL, 'target of the migrator', null);
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginname = $input->getArgument('pluginname');
+        $branch = $input->getOption('branch');
+        $target = $input->getOption('target');
+        $verbose = $input->getOption('verbose');
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugin = $this->findPluginByName($pluginManager, $pluginname);
+        if (null === $plugin) {
+            $output->writeln('<error>Could not find plugin of that name.</error>');
+
+            return Command::FAILURE;
+        }
+
+        $pluginpath = \Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
+
+        if (!is_dir($pluginpath . '/migrations')) {
+            $output->writeln('<comment>Could not find any migrations of that plugin.</comment>');
+
+            return Command::SUCCESS;
+        }
+
+        // if there are migrations, migrate
+        $schemaVersion = new \DBSchemaVersion($plugin['name'], $branch);
+        $migrator = new \Migrator($pluginpath . '/migrations', $schemaVersion, $verbose);
+
+        $migrations = $migrator->relevantMigrations($target);
+
+        $rows = [];
+        foreach ($migrations as $number => $migration) {
+            $description = $migration->description() ?: '(no description)';
+            $rows[] = [$number, get_class($migration), $description];
+        }
+
+        $table = new Table($output);
+        $table->setHeaders(['ID', 'Class', 'Description'])->setRows($rows);
+        $table->setStyle('box');
+        $table->render();
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginMigrate.php b/cli/Commands/Plugins/PluginMigrate.php
new file mode 100644
index 0000000000000000000000000000000000000000..8bfd03663cb9f1007a896566753cca64ae79e23e
--- /dev/null
+++ b/cli/Commands/Plugins/PluginMigrate.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginMigrate extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:migrate';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Migrate a plugin.');
+        $this->setHelp('This command migrates a plugin.');
+        $this->addArgument('pluginname', InputArgument::REQUIRED, 'name of the plugin');
+        $this->addOption('branch', 'b', InputOption::VALUE_OPTIONAL, 'branch of the migrations', '0');
+        $this->addOption(
+            'target',
+            't',
+            InputOption::VALUE_REQUIRED,
+            'the id number of the migration to migrate to',
+            null
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginname = $input->getArgument('pluginname');
+        $branch = $input->getOption('branch');
+        $target = $input->getOption('target');
+        $verbose = $input->getOption('verbose');
+
+        if (null !== $target) {
+            $target = (int) $target;
+        }
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugin = $this->findPluginByName($pluginManager, $pluginname);
+        if (null === $plugin) {
+            $output->writeln('<error>Could not find plugin of that name.</error>');
+
+            return Command::FAILURE;
+        }
+
+        $pluginpath = \Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
+
+        if (!is_dir($pluginpath . '/migrations')) {
+            $output->writeln('<comment>Could not find any migrations of that plugin.</comment>');
+
+            return Command::SUCCESS;
+        }
+
+        // if there are migrations, migrate
+        $schemaVersion = new \DBSchemaVersion($plugin['name'], $branch);
+        $migrator = new \Migrator($pluginpath . '/migrations', $schemaVersion, $verbose);
+
+        $migrator->migrateTo($target);
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginRegister.php b/cli/Commands/Plugins/PluginRegister.php
new file mode 100644
index 0000000000000000000000000000000000000000..5167fa3939f060b7b004a66f0568a40ea93391e6
--- /dev/null
+++ b/cli/Commands/Plugins/PluginRegister.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginRegister extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:register';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Register a plugin.');
+        $this->setHelp('This command registers an installed plugin.');
+        $this->addArgument('pluginpath', InputArgument::REQUIRED, 'path to the plugin');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginpath = $input->getArgument('pluginpath');
+        $pluginManager = \PluginManager::getInstance();
+        $manifest = $pluginManager->getPluginManifest($pluginpath);
+        if (!$manifest) {
+            $output->writeln('<error>The plugin\'s manifest is missing.</error>');
+            return Command::FAILURE;
+        }
+
+        // get plugin meta data
+        $pluginclass = $manifest['pluginclassname'];
+        $origin = $manifest['origin'];
+        $minVersion = $manifest['studipMinVersion'];
+        $maxVersion = $manifest['studipMaxVersion'];
+
+        // check for compatible version
+        if (
+            (isset($minVersion) && \StudipVersion::olderThan($minVersion)) ||
+            (isset($maxVersion) && \StudipVersion::newerThan($maxVersion))
+        ) {
+            $output->writeln('<error>The plugin is not compatible with this version of Stud.IP.</error>');
+            return Command::FAILURE;
+        }
+
+        // determine the plugin path
+        $basepath = \Config::get()->PLUGINS_PATH;
+        $pluginpath = $origin . '/' . $pluginclass;
+        $pluginregistered = $pluginManager->getPluginInfo($pluginclass);
+
+        // create database schema if needed
+        if (isset($manifest['dbscheme']) && !$pluginregistered) {
+            $schemafile = $pluginpath . '/' . $manifest['dbscheme'];
+            $contents = file_get_contents($schemafile);
+            $statements = preg_split("/;[[:space:]]*\n/", $contents, -1, PREG_SPLIT_NO_EMPTY);
+            $db = \DBManager::get();
+            foreach ($statements as $statement) {
+                $db->exec($statement);
+            }
+        }
+
+        // check for migrations
+        if (is_dir($pluginpath . '/migrations')) {
+            $schemaVersion = new \DBSchemaVersion($manifest['pluginname']);
+            $migrator = new \Migrator($pluginpath . '/migrations', $schemaVersion);
+            $migrator->migrateTo(null);
+        }
+
+        // now register the plugin in the database
+        $pluginid = $pluginManager->registerPlugin($manifest['pluginname'], $pluginclass, $pluginpath);
+
+        // register additional plugin classes in this package
+        $additionalclasses = $manifest['additionalclasses'];
+
+        if (is_array($additionalclasses)) {
+            foreach ($additionalclasses as $class) {
+                $pluginManager->registerPlugin($class, $class, $pluginpath, $pluginid);
+            }
+        }
+
+        $output->writeln('The plugin was successfully registered.');
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginScan.php b/cli/Commands/Plugins/PluginScan.php
new file mode 100644
index 0000000000000000000000000000000000000000..72b443fe1c3f358cbb777daa9d4ab0fc13daea95
--- /dev/null
+++ b/cli/Commands/Plugins/PluginScan.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginScan extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:scan';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Scans for unregistered plugins.');
+        $this->setHelp(
+            'This command scans the plugin path for plugin.manifest files belonging to not registered plugins.'
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginAdmin = new \PluginAdministration();
+        $pluginManager = \PluginManager::getInstance();
+        foreach ($pluginAdmin->scanPluginDirectory() as $manifest) {
+            if (!$pluginManager->getPluginInfo($manifest['pluginclassname'])) {
+                $pairs = [];
+                foreach ($manifest as $key => $value) {
+                    $pairs[] = [$key, is_array($value) ? join(",", $value) : (string) $value];
+                }
+
+                $table = new Table($output);
+                $table->setHeaders(['Field', 'Value'])->setRows($pairs);
+                $table->setStyle('box');
+                $table->render();
+
+                $output->writeln('');
+            }
+        }
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginStatusMigrations.php b/cli/Commands/Plugins/PluginStatusMigrations.php
new file mode 100644
index 0000000000000000000000000000000000000000..8bc087522dfd0fd5f845e66781ef9c21705c3824
--- /dev/null
+++ b/cli/Commands/Plugins/PluginStatusMigrations.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginStatusMigrations extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:status-migrations';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Shows the state of all migrations of a plugin.');
+        $this->setHelp('This command shows the state of all migrations of a plugin.');
+        $this->addArgument('pluginname', InputArgument::REQUIRED, 'name of the plugin');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginname = $input->getArgument('pluginname');
+        $verbose = $input->getOption('verbose');
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugin = $this->findPluginByName($pluginManager, $pluginname);
+        if (null === $plugin) {
+            $output->writeln('<error>Could not find plugin of that name.</error>');
+
+            return Command::FAILURE;
+        }
+
+        $pluginpath = \Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
+
+        if (!is_dir($pluginpath . '/migrations')) {
+            $output->writeln('<comment>Could not find any migrations of that plugin.</comment>');
+
+            return Command::SUCCESS;
+        }
+
+        // if there are migrations, migrate
+        $schemaVersion = new \DBSchemaVersion($plugin['name']);
+        $migrator = new \Migrator($pluginpath . '/migrations', $schemaVersion, $verbose);
+
+
+        $migrations = $migrator->migrationClasses();
+        uksort($migrations, 'version_compare');
+        $migrations = array_reverse($migrations, true);
+
+        $pending = $migrator->relevantMigrations(null);
+
+        $rows = [];
+        foreach ($migrations as $number => $migration) {
+            $rows[] = [isset($pending[$number]) ? 'No' : 'Yes', $number, basename($migration[0], '.php')];
+        }
+
+        $table = new Table($output);
+        $table->setHeaders(['Ran?', 'ID', 'Migration'])->setRows($rows);
+        $table->setStyle('box');
+        $table->render();
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Plugins/PluginUnregister.php b/cli/Commands/Plugins/PluginUnregister.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b08c93770c8bbcbb6fe7d77b492ebd0350a9557
--- /dev/null
+++ b/cli/Commands/Plugins/PluginUnregister.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Studip\Cli\Commands\Plugins;
+
+use Studip\Cli\Commands\AbstractPluginCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class PluginUnregister extends AbstractPluginCommand
+{
+    protected static $defaultName = 'plugin:unregister';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Unregister a plugin.');
+        $this->setHelp('This command unregisters a plugin.');
+        $this->addArgument('pluginname', InputArgument::REQUIRED, 'name of the plugin');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $pluginname = $input->getArgument('pluginname');
+        $verbose = $input->getOption('verbose');
+
+        $pluginManager = \PluginManager::getInstance();
+        $plugin = $this->findPluginByName($pluginManager, $pluginname);
+        if (null === $plugin) {
+            $output->writeln('<error>Could not find plugin of that name.</error>');
+
+            return Command::FAILURE;
+        }
+
+        $pluginManager->unregisterPlugin($plugin['id']);
+
+        // if there are any migrations, un-migrate
+        $pluginpath = \Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
+        if (is_dir($pluginpath . '/migrations')) {
+            $schemaVersion = new \DBSchemaVersion($plugin['name']);
+            $migrator = new \Migrator($pluginpath . '/migrations', $schemaVersion, $verbose);
+
+            $migrator->migrateTo(0);
+        }
+
+        $output->writeln('The plugin was unregistered successfully.');
+
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/Resources/UpdateBookingIntervals.php b/cli/Commands/Resources/UpdateBookingIntervals.php
new file mode 100644
index 0000000000000000000000000000000000000000..0d0121126ef274b6ab2895ebdcf920d5a1624c16
--- /dev/null
+++ b/cli/Commands/Resources/UpdateBookingIntervals.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Studip\Cli\Commands\Resources;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class UpdateBookingIntervals extends Command
+{
+    protected static $defaultName = 'resources:update-booking-intervals';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Update booking intervals.');
+        $this->addOption(
+            'remove-exceptions',
+            'r',
+            InputOption::VALUE_OPTIONAL,
+            'exceptions for a booking with repetitions twill be removed',
+            false
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $keep_exceptions = $input->getOption('remove-exceptions');
+        if ($keep_exceptions !== false) {
+            $keep_exceptions = true;
+        }
+        $bookings = \ResourceBooking::findBySql('TRUE');
+
+        if ($bookings) {
+            foreach ($bookings as $booking) {
+                $booking->updateIntervals($keep_exceptions);
+            }
+            $output->writeln('The resource_booking_intervals table is up to date again!');
+        } else {
+            $output->writeln('There are no bookings in your database! Nothing to do!');
+        }
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/Commands/SORM/DescribeModels.php b/cli/Commands/SORM/DescribeModels.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d1256ff300a3c0abf57a7b256486cabb2b62a3e
--- /dev/null
+++ b/cli/Commands/SORM/DescribeModels.php
@@ -0,0 +1,285 @@
+<?php
+namespace Studip\Cli\Commands\SORM;
+
+use SimpleORMapCollection;
+use Studip\Cli\Commands\AbstractCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+final class DescribeModels extends AbstractCommand
+{
+    protected static $defaultName = 'sorm:describe';
+
+    private $progress;
+
+    protected function configure(): void
+    {
+        $this->setDescription('Describe models');
+        $this->setHelp('This command will add neccessary @property annotations to SimpleORMap classes in a folder');
+
+        $this->addArgument(
+            'folder',
+            InputArgument::OPTIONAL,
+            'Folder to scan (will default to lib/models)',
+            'lib/models'
+        );
+
+        $this->addOption(
+            'recursive',
+            'r',
+            InputOption::VALUE_NEGATABLE,
+            'Scan into subfolders recursively'
+        );
+        $this->addOption(
+            'bootstrap',
+            'b',
+            InputOption::VALUE_OPTIONAL,
+            'Execute bootstrap file before scanning folder'
+        );
+    }
+
+    public function execute(InputInterface $input, OutputInterface $output)
+    {
+        $bootstrap = $input->getOption('bootstrap');
+        if ($bootstrap) {
+            if (!file_exists($bootstrap)) {
+                throw new \Exception("Invalid bootstrap file {$bootstrap} provided");
+            }
+            require_once $bootstrap;
+        }
+
+        $recursive = $input->getOption('recursive') ?? false;
+        $folder = $input->getArgument('folder');
+        $iterator = $this->getFolderIterator($folder, $recursive, ['php']);
+
+        $this->progress = new ProgressBar($output, iterator_count($iterator));
+        $this->progress->start();
+
+        foreach ($iterator as $file) {
+            $filename = $file->getFilename();
+
+            if (!is_writable($file->getRealPath())) {
+                $this->outputForFile(
+                    $output,
+                    '<comment>Skipping not writable file ' . $this->relativeFilePath($file->getPathname()) . '</comment>'
+                );
+                continue;
+            }
+
+            $class_name = str_replace('.class.php', '.php', $filename);
+            $class_name = pathinfo($class_name, PATHINFO_FILENAME);
+            if (!class_exists($class_name)) {
+                $class_name = $this->getClassNameFromFile($file->getPathname()) ?? $class_name;
+            }
+
+            if (!class_exists($class_name) || !is_subclass_of($class_name, \SimpleORMap::class)) {
+                $this->outputForFile(
+                    $output,
+                    "Skipping invalid class file {$filename} (class {$class_name})",
+                    OutputInterface::VERBOSITY_VERBOSE
+                );
+                continue;
+            }
+
+            try {
+                $reflection = new \ReflectionClass($class_name);
+            } catch (\Error $e) {
+                $this->outputForFile(
+                    $output,
+                    "<error>Could not get reflection for class {$class_name} ({$e->getMessage()})</error>"
+                );
+                continue;
+            }
+
+            if ($reflection->isAbstract()) {
+                $this->outputForFile(
+                    $output,
+                    "Skipping abstract class {$class_name}",
+                    OutputInterface::VERBOSITY_VERBOSE
+                );
+                continue;
+            }
+
+            $model = $reflection->newInstance();
+            $meta = $model->getTableMetaData();
+
+            $properties = [];
+
+            foreach ($meta['fields'] as $field => $info) {
+                $name = mb_strtolower($field);
+                $type = $this->getPHPType($info);
+                $properties[$name] = [
+                    'type'        => $type,
+                    'description' => 'database column',
+                ];
+                if ($alias = array_search($name, $meta['alias_fields'])) {
+                    $properties[$alias] = [
+                        'type'        => $type,
+                        'description' => "alias column for {$name}",
+                    ];
+                }
+            }
+
+            foreach ($meta['relations'] as $relation) {
+                $options = $model->getRelationOptions($relation);
+                $related_class_name = $options['class_name'];
+                if (in_array($options['type'], ['has_many', 'has_and_belongs_to_many'])) {
+                    $related_class_name = SimpleORMapCollection::class;
+                }
+
+                if ($reflection->inNamespace()) {
+                    $related_class_name = "\\{$related_class_name}";
+                    if (mb_strpos($related_class_name, "\\{$reflection->getNamespaceName()}") === 0) {
+                        $related_class_name = substr($related_class_name, strlen($reflection->getNamespaceName()) + 2);
+                    }
+                }
+
+                $properties[$relation] = [
+                    'type'        => $related_class_name,
+                    'description' => "{$options['type']} {$options['class_name']}",
+                ];
+            }
+
+            if ($this->updateDocBlockOfClass($reflection, $properties)) {
+                $this->outputForFile(
+                    $output,
+                    '<info>Updated ' . $this->relativeFilePath($file->getPathname()) . '</info>'
+                );
+            } else {
+                $this->outputForFile(
+                    $output,
+                    'No changes in ' . $this->relativeFilePath($file->getPathname()),
+                    OutputInterface::VERBOSITY_VERBOSE
+                );
+            }
+        }
+
+        $this->progress->clear();
+
+        return Command::SUCCESS;
+    }
+
+    private function outputForFile($output, ...$args)
+    {
+        $this->progress->advance();
+        $this->progress->clear();
+        $output->writeln(...$args);
+        $this->progress->display();
+
+    }
+
+    private function getPHPType($info)
+    {
+        if (preg_match('/^(?:tiny|small|medium|big)?int(?:eger)?/iS', $info['type'])) {
+            return 'int';
+        }
+
+        if (preg_match('/^(?:decimal|double|float|numeric)/iS', $info['type'])) {
+            return 'float';
+        }
+
+        if (preg_match('/^bool(?:ean)?/iS', $info['type'])) {
+            return 'bool';
+        }
+
+        return 'string';
+    }
+
+    private function updateDocBlockOfClass(\ReflectionClass $reflection, array $properties): bool
+    {
+        $has_docblock = (bool) $reflection->getDocComment();
+        $docblock     = $reflection->getDocComment() ?: $this->getDefaultDocblock();
+
+        $docblock_lines = array_map('rtrim', explode("\n", $docblock));
+
+        $properties_started = false;
+        $docblock_lines = array_filter($docblock_lines, function ($line) use (&$properties_started) {
+            $line = ltrim($line, '* ');
+            if ($properties_started) {
+                return $line === '/';
+            }
+
+            $properties_started = strpos($line, '@property ') === 0;
+            return !$properties_started;
+        });
+
+        $docblock_lines = array_reverse($docblock_lines);
+        while ($docblock_lines[1] === ' *') {
+            array_splice($docblock_lines, 1, 1);
+        }
+        $docblock_lines = array_reverse($docblock_lines);
+
+        $properties = array_map(function ($variable, $property) {
+            return " * @property {$property['type']} \${$variable} {$property['description']}";
+        }, array_keys($properties), array_values($properties));
+
+        array_unshift($properties, ' *');
+        array_splice($docblock_lines, -1, 0, $properties);
+
+        $new_docblock = implode("\n", $docblock_lines);
+
+        if ($docblock === $new_docblock) {
+            return false;
+        }
+
+        $contents = file_get_contents($reflection->getFileName());
+        if ($has_docblock) {
+            $contents = str_replace($docblock, $new_docblock, $contents);
+        } else {
+            $contents = preg_replace(
+                '/^class/m',
+                $new_docblock . "\nclass",
+                $contents,
+                1
+            );
+        }
+
+        file_put_contents($reflection->getFileName(), $contents);
+
+        return true;
+    }
+
+    private function getDefaultDocBlock(): string
+    {
+        return implode("\n", [
+            '/**',
+            ' * @license GPL2 or any later version',
+            ' */',
+        ]);
+    }
+
+    /**
+     * @see https://stackoverflow.com/a/14250011
+     */
+    private function getClassNameFromFile(string $filename): ?string
+    {
+        $code = file_get_contents($filename);
+        $tokens = token_get_all($code);
+
+        $namespace = '';
+
+        for ($i = 0, $l = count($tokens); $i < $l; $i += 1) {
+            if ($tokens[$i][0] === T_NAMESPACE) {
+                for ($j= $i + 1; $j < $l; $j += 1) {
+                    if ($tokens[$j][0] === T_STRING) {
+                        $namespace .= "\\" . $tokens[$j][1];
+                    } elseif ($tokens[$j] === '{' || $tokens[$j] === ';') {
+                        break;
+                    }
+                }
+            }
+            if ($tokens[$i][0] === T_CLASS) {
+                for ($j = $i + 1; $j < $l; $j += 1) {
+                    if ($tokens[$j] === '{') {
+                        return ltrim($namespace . "\\" . $tokens[$i + 2][1], "\\");
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/cli/Commands/Translations/VueGettextSplitTranslations.php b/cli/Commands/Translations/VueGettextSplitTranslations.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9579f203f169e568a6b5ce794e4b8f25bc8e3fe
--- /dev/null
+++ b/cli/Commands/Translations/VueGettextSplitTranslations.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Studip\Cli\Commands\Translations;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class VueGettextSplitTranslations extends Command
+{
+    protected static $defaultName = 'translations:vue-gettext-split';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Split vue-gettext.');
+        $this->setHelp('Split vue-gettext translations');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $translationsFile = $GLOBALS['STUDIP_BASE_PATH'] . '/resources/locales/translations.json';
+        if (file_exists($translationsFile)) {
+            $file = file_get_contents($translationsFile);
+            $json = json_decode($file, true);
+            foreach ($json as $lang => $content) {
+                $langFile = realpath(__DIR__ . '/../resources/locales/') . '/' . $lang . '.json';
+                file_put_contents($langFile, json_encode($content));
+            }
+            return Command::SUCCESS;
+        } else {
+            $output->writeln(sprintf('<error>Could not find translations in %s</error>', $translationsFile));
+            return Command::FAILURE;
+        }
+    }
+}
diff --git a/cli/Commands/Users/UserDelete.php b/cli/Commands/Users/UserDelete.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ba854e60d8c0d61edb37e8928a424174306ff31
--- /dev/null
+++ b/cli/Commands/Users/UserDelete.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Studip\Cli\Commands\Users;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class UserDelete extends Command
+{
+    protected static $defaultName = 'user:delete';
+
+    protected function configure(): void
+    {
+        $this->setDescription('Delete users.');
+        $this->setHelp('Delete multiple studip user accounts');
+        $this->addArgument('range', InputArgument::REQUIRED, 'Username or path to csv-file');
+        $this->addOption(
+            'file_range',
+            'f',
+            InputOption::VALUE_OPTIONAL,
+            'Set to true, if you want use a txt file with username',
+            true
+        );
+        $this->addOption('email', 'e', InputOption::VALUE_OPTIONAL, 'Send a deletion email', true);
+        $this->addOption(
+            'delete_admins',
+            'd',
+            InputOption::VALUE_OPTIONAL,
+            'Admins can also be deleted on request',
+            false
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $range = $input->getArgument('range');
+        $file_range = $input->getOption('file_range');
+        $email = $input->getOption('email');
+        $delete_admins = $input->getOption('delete_admins');
+
+        if ($email && !($MAIL_LOCALHOST && $MAIL_HOST_NAME && $ABSOLUTE_URI_STUDIP)) {
+            $output->writeln(
+                "<error>To use this script you MUST set correct values for $MAIL_LOCALHOST, $MAIL_HOST_NAME and $ABSOLUTE_URI_STUDIP in local.inc!</error>"
+            );
+        }
+
+        if (!(bool) $file_range) {
+            $usernames = [$range];
+        } else {
+            if (!is_file($range)) {
+                $output->writeln(sprintf('<error>File not found: %s</error>', $range));
+                return Command::FAILURE;
+            }
+            $file = fopen($range, 'r');
+            $list = '';
+            while (!feof($file)) {
+                $list .= fgets($file, 1024);
+            }
+            $usernames = preg_split('/[\s,;]+/', $list, -1, PREG_SPLIT_NO_EMPTY);
+            $usernames = array_unique($usernames);
+        }
+
+        $users = \User::findBySQL('username IN (?)', [$usernames]);
+
+        if (!empty($users)) {
+            foreach ($users as $user) {
+                if (!$delete_admins && ($user->perms == 'admin' || $user->perms == 'root')) {
+                    $output->writeln(sprintf('User: %s is %s, NOT deleted', $user->username, $user->perms));
+                } else {
+                    $umanager = new \UserManagement($user->id);
+                    //wenn keine Email gewünscht, Adresse aus den Daten löschen
+                    if (!$email) {
+                        $umanager->user_data['auth_user_md5.Email'] = '';
+                    }
+                    if ($umanager->deleteUser()) {
+                        $output->writeln(sprintf('<info>User: %s successfully deleted:</info>', $user->username));
+                    } else {
+                        $output->writeln(sprintf('<error>User: %s NOT deleted</error>', $user->username));
+                    }
+                    $output->writeln(parse_msg_to_clean_text($umanager->msg));
+                }
+            }
+        }
+        return Command::SUCCESS;
+    }
+}
diff --git a/cli/antelope_to_barracuda.php b/cli/antelope_to_barracuda.php
deleted file mode 100755
index 4360ffd4a68ea359767b780d474e0685758f8cda..0000000000000000000000000000000000000000
--- a/cli/antelope_to_barracuda.php
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once(__DIR__.'/studip_cli_env.inc.php');
-
-echo 'Migration starting at '.date('d.m.Y H:i:s').".\n";
-$start = microtime(true);
-
-global $DB_STUDIP_DATABASE;
-
-// Tables to ignore on engine conversion.
-$ignore_tables = [];
-
-// Check if InnoDB is enabled in database server.
-$engines = DBManager::get()->fetchAll("SHOW ENGINES");
-$innodb = false;
-foreach ($engines as $e) {
-    // InnoDB is found and enabled.
-    if ($e['Engine'] == 'InnoDB' && in_array(mb_strtolower($e['Support']), ['default', 'yes'])) {
-        $innodb = true;
-        break;
-    }
-}
-
-if ($innodb) {
-    // Get version of database system (MySQL/MariaDB/Percona)
-    $data = DBManager::get()->fetchFirst("SELECT VERSION() AS version");
-    $version = $data[0];
-
-    // Use Barracuda format if database supports it (5.5 upwards).
-    if (version_compare($version, '5.5', '>=')) {
-        echo "\tChecking if Barracuda file format is supported...";
-        // Get innodb_file_per_table setting
-        $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_per_table'");
-        $file_per_table = $data['Value'];
-
-        // Check if Barracuda file format is enabled
-        $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_format'");
-        $file_format = $data['Value'];
-
-        if (mb_strtolower($file_per_table) == 'on' && mb_strtolower($file_format) == 'barracuda') {
-
-            echo " yes.\n";
-
-            // Fetch all tables that need to be converted.
-            $tables = DBManager::get()->fetchFirst("SELECT TABLE_NAME
-                FROM `information_schema`.TABLES
-                WHERE TABLE_SCHEMA=:database AND ENGINE=:engine
-                    AND ROW_FORMAT IN (:rowformats)
-                ORDER BY TABLE_NAME",
-                [
-                    ':database' => $DB_STUDIP_DATABASE,
-                    ':engine' => 'InnoDB',
-                    ':rowformats' => ['Compact', 'Redundant']
-                ]);
-
-            $newformat = 'DYNAMIC';
-
-            // Prepare query for table conversion.
-            $stmt = DBManager::get()->prepare("ALTER TABLE :database.:table ROW_FORMAT=:newformat");
-            $stmt->bindParam(':database', $DB_STUDIP_DATABASE, StudipPDO::PARAM_COLUMN);
-            $stmt->bindParam(':newformat', $newformat, StudipPDO::PARAM_COLUMN);
-
-            if (count($tables) > 0) {
-
-                // Now convert the found tables.
-                foreach ($tables as $t) {
-                    $local_start = microtime(true);
-                    $stmt->bindParam(':table', $t, StudipPDO::PARAM_COLUMN);
-                    $stmt->execute();
-                    $local_end = microtime(true);
-                    $local_duration = $local_end - $local_start;
-                    $human_local_duration = sprintf("%02d:%02d:%02d",
-                        ($local_duration / 60 / 60) % 24, ($local_duration / 60) % 60, $local_duration % 60);
-
-                    echo "\tConversion of table " . $t . " took " . $human_local_duration . ".\n";
-                }
-
-            } else {
-                echo "\tNo Antelope format tables found.\n";
-            }
-
-        } else {
-            echo " no:\n";
-            if (mb_strtolower($file_per_table) != 'on') {
-                echo "\t- file_per_table not set\n";
-            }
-            if (mb_strtolower($file_format) != 'barracuda') {
-                echo "\t- file_format not set to Barracuda (but to " . $file_format . ")\n";
-            }
-        }
-
-        $end = microtime(true);
-
-        $duration = $end - $start;
-        $human_duration = sprintf("%02d:%02d:%02d",
-            ($duration / 60 / 60) % 24, ($duration / 60) % 60, $duration % 60);
-
-        echo 'Migration finished at ' . date('d.m.Y H:i:s') . ', duration ' . $human_duration . ".\n";
-
-    } else {
-        echo "Your database server does not yet support the Barracuda row format (you need at least 5.5).\n";
-    }
-
-} else {
-    echo "The storage engine InnoDB is not enabled in your ".
-        "database installation, tables cannot be converted.\n";
-}
diff --git a/cli/biest7783-fix.php b/cli/biest7783-fix.php
deleted file mode 100755
index e3cad62b0889ded50e60d958beebbb3653ebd9e5..0000000000000000000000000000000000000000
--- a/cli/biest7783-fix.php
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script removes all members from a course that should not have been
- * members in the first place.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @see    https://develop.studip.de/trac/ticket/7783
- */
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-require_once __DIR__ . '/../config/config_local.inc.php';
-
-function output($what) {
-    if (StudipVersion::olderThan(4)) {
-        $what = studip_utf8encode($what);
-    }
-
-    fwrite(STDOUT, $what);
-}
-
-$opts    = getopt('d', ['dry-run']);
-$dry_run = isset($opts['d']) || isset($opts['dry-run']);
-
-// Reduce arguments by options (this is far from perfect)
-$args = $_SERVER['argv'];
-$arg_stop = array_search('--', $args);
-if ($arg_stop !== false) {
-    $args = array_slice($args, $arg_stop + 1);
-} elseif (count($opts)) {
-    $args = array_slice($args, 1 + count($opts));
-} else {
-    $args = array_slice($args, 1);
-}
-
-if (count($args) < 1) {
-    output("Fix for Biest 7783 - Use {$argv[0]} [--dry-run/-d] <semester_id,current,next>\n");
-    exit(0);
-}
-
-$semester_ids = explode(',', implode(',', array_map('trim', $args)));
-foreach ($semester_ids as $index => $semester_id) {
-    if ($semester_id === 'current') {
-        $semester_id = Semester::findCurrent()->id;
-    } elseif ($semester_id === 'next') {
-        $semester_id = Semester::findNext()->id;
-    } elseif (Semester::find($semester_id) === null) {
-        output("Semester id {$semester_id} is invalid\n");
-        exit(0);
-    }
-
-    $semester_ids[$index] = $semester_id;
-}
-
-$query = "SELECT DISTINCT
-              cs.`set_id`, s.`seminar_id`
-          FROM `semester_data` AS sd
-          JOIN `seminare` AS s
-            ON (s.`start_time` <= sd.`beginn`
-                AND (
-                    sd.`beginn` <= s.`start_time` + s.`duration_time`
-                    OR s.`duration_time` = -1
-                )
-            )
-          JOIN `seminar_courseset` AS scs USING (`seminar_id`)
-          JOIN `coursesets` AS cs USING (`set_id`)
-          JOIN `auth_user_md5` USING (`user_id`)
-          JOIN `courseset_rule` AS csr USING (`set_id`)
-          JOIN `admission_condition` AS ac USING (`rule_id`)
-          JOIN `userfilter` AS uf USING (`filter_id`)
-          JOIN `userfilter_fields` AS uff USING (`filter_id`)
-          WHERE `semester_id` IN (:semester_ids)
-            AND `algorithm_run` = 0
-            AND uff.`type` = 'SemesterOfStudyCondition'
-            AND uff.`value` > 1
-          ORDER BY cs.`name` ASC, s.`name`";
-$statement = DBManager::get()->prepare($query);
-$statement->bindValue(':semester_ids', $semester_ids);
-$statement->execute();
-$sets = $statement->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_COLUMN);
-
-foreach ($sets as $set_id => $course_ids) {
-    $courseset = new CourseSet($set_id);
-    $remove = [];
-    foreach ($course_ids as $course_id) {
-        $course = Course::find($course_id);
-        $members = new MembersModel($course_id, $course->getFullname());
-        $applicants = $members->getAdmissionMembers();
-        foreach (['awaiting', 'claiming'] as $status) {
-            foreach ($applicants[$status] as $applicant) {
-                $errors = $courseset->checkAdmission($applicant->user_id, $course_id);
-                if (count($errors) === 0) {
-                    continue;
-                }
-
-                if (!isset($remove[$course_id])) {
-                    $remove[$course_id] = [
-                        'course'  => $course,
-                        'members' => $members,
-                        'status'  => [],
-                    ];
-                }
-                if (!isset($remove[$course_id]['status'][$status])) {
-                    $remove[$course_id]['status'][$status] = [];
-                }
-
-                $remove[$course_id]['status'][$status][] = User::find($applicant['user_id']);
-            }
-        }
-    }
-
-    if ($remove) {
-        $owner = User::find($courseset->getUserId())->getFullname();
-        output("= Anmeldeset {$courseset->getName()} ({$owner}):\n");
-
-        foreach ($remove as $row) {
-            output("  - Veranstaltung {$row['course']->getFullname()}:\n");
-            foreach ($row['status'] as $status => $users) {
-                $user_ids = array_map(function (User $user) {
-                    return $user->id;
-                }, $users);
-
-                if ($dry_run) {
-                    foreach ($users as $user) {
-                        output("    - Nutzer {$user->getFullname()}\n");
-                    }
-                } else {
-                    $result = $row['members']->cancelAdmissionSubscription($user_ids, $status);
-                    foreach ($result as $row) {
-                        output("    - Nutzer {$row}\n");
-                    }
-                }
-
-            }
-        }
-    }
-}
diff --git a/cli/biest7789-fix.php b/cli/biest7789-fix.php
deleted file mode 100755
index e94a9f35d143e92140f14f14e27293892c1f78b8..0000000000000000000000000000000000000000
--- a/cli/biest7789-fix.php
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script converts selected database columns from php serialization to json
- *
- * @author Till Glöggler <studip@tillgloeggler.de>
- * @see    https://develop.studip.de/trac/ticket/7789
- */
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-require_once __DIR__ . '/../config/config_local.inc.php';
-
-ini_set('default_charset', 'utf-8');
-
-function legacy_studip_utf8encode($data)
-{
-    if (is_array($data)) {
-        $new_data = [];
-        foreach ($data as $key => $value) {
-            $key = legacy_studip_utf8encode($key);
-            $new_data[$key] = legacy_studip_utf8encode($value);
-        }
-        return $new_data;
-    }
-
-    if (!preg_match('/[\200-\377]/', $data) && !preg_match("'&#[0-9]+;'", $data)) {
-        return $data;
-    } else {
-        return mb_decode_numericentity(
-            mb_convert_encoding($data,'UTF-8', 'WINDOWS-1252'),
-            [0x100, 0xffff, 0, 0xffff],
-            'UTF-8'
-        );
-    }
-}
-
-
-function convert_to_json($table, $column, $where = null)
-{
-    $db = DBManager::get();
-
-    echo "\n\n /*************************************************\n";
-    echo " ***** " . $table ." ***** ";
-    echo "\n *************************************************/\n\n";
-
-    // get primary keys
-    $result = $db->query("SHOW KEYS FROM $table WHERE Key_name = 'PRIMARY'");
-    $keys = [];
-
-    while ($data = $result->fetch(PDO::FETCH_ASSOC)) {
-        $keys[] = $data['Column_name'];
-    }
-
-    // retrieve and convert data
-    $result = $db->query("SELECT `". implode('`,`', $keys) ."`, `$column` FROM `$table` WHERE ". ($where ?: '1'));
-
-    while ($data = $result->fetch(PDO::FETCH_ASSOC)) {
-        $content = unserialize(legacy_studip_utf8decode($data[$column]));
-
-        if ($content === false) {
-            // try to fix string length denotations
-            $fixed = preg_replace_callback(
-                '/s:([0-9]+):\"(.*?)\";/s',
-                function ($matches) { return "s:".strlen($matches[2]).':"'.$matches[2].'";';     },
-                $data[$column]
-            );
-
-            $content = unserialize(legacy_studip_utf8decode($fixed));
-        }
-
-        if ($content !== false) {
-            // encode all data
-            $json = json_encode(legacy_studip_utf8encode($content), true);
-
-            $query = "UPDATE `$table` SET `$column` = ". $db->quote($json) ."\n WHERE ";
-
-            $where_query = [];
-            foreach ($keys as $key) {
-                $where_query[] = "`$key` = ". $db->quote($data[$key]);
-            }
-
-            $q = $query . implode(' AND ', $where_query);
-            $db->exec($q);
-            echo $q .";\n";
-        } else {
-            echo '/* Could not convert: '. print_r($data, 1) ." */\n";
-        }
-    }
-}
-
-convert_to_json('extern_config', 'config');
-convert_to_json('aux_lock_rules', 'attributes');
-convert_to_json('aux_lock_rules', 'sorting');
-convert_to_json('user_config', 'value', "field = 'MY_COURSES_ADMIN_VIEW_FILTER_ARGS'");
-convert_to_json('mail_queue_entries', 'mail');
diff --git a/cli/biest7866-fix.php b/cli/biest7866-fix.php
deleted file mode 100755
index ca1f90c094dad07855a0b3ca3ea06dc567902df8..0000000000000000000000000000000000000000
--- a/cli/biest7866-fix.php
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script sets folder range_ids to the range_ids of their parent folder.
- *
- * @author Thomas Hackl <thomas.hackl@uni-passau.de>
- * @see    https://develop.studip.de/trac/ticket/7866
- */
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-
-/**
- * Sets the range_id of all child folders to the given range_id.
- * @param $parent_folder
- * @param $range_id
- */
-function setFolderRangeId($parent_folder, $range_id) {
-    // Update all child folder range_ids.
-    DBManager::get()->execute(
-        "UPDATE `folders` SET `range_id` = :range WHERE `parent_id` = :parent",
-        [
-            'range' => $range_id,
-            'parent' => $parent_folder
-        ]
-    );
-
-    // Recursion: set correct range_id for child folders with wrong range_id.
-    $children = DBManager::get()->fetchAll(
-        "SELECT `id`, `range_id` FROM `folders` WHERE `parent_id` = :parent",
-        [
-            'parent' => $parent_folder
-        ]
-    );
-    foreach ($children as $child) {
-        if ($child['range_id'] != $range_id) {
-            echo sprintf("Folder %s -> range_id %s.\n", $child['id'], $range_id);
-        }
-        setFolderRangeId($child['id'], $range_id);
-    }
-}
-
-// Fetch all root folders and process their children recursively.
-$root_folders = DBManager::get()->fetchAll("SELECT `id`, `range_id` FROM `folders` WHERE `parent_id` = ''");
-
-foreach ($root_folders as $r) {
-    setFolderRangeId($r['id'], $r['range_id']);
-}
diff --git a/cli/biest8136-fix.php b/cli/biest8136-fix.php
deleted file mode 100755
index c1bad9a5c81b725cfc5f25df4dd8ab76cedfde5f..0000000000000000000000000000000000000000
--- a/cli/biest8136-fix.php
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script adjusts all activities so that anonymous posts will actually be
- * anonymous.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @see    https://develop.studip.de/trac/ticket/8136
- */
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-require_once __DIR__ . '/../config/config_local.inc.php';
-
-$query = "UPDATE `activities`
-          SET `actor_type` = 'anonymous',
-              `actor_id` = ''
-          WHERE `provider` = :provider
-            AND `actor_type` != 'anonymous'
-            AND `object_id` IN (
-                SELECT `topic_id`
-                FROM `forum_entries`
-                WHERE `anonymous` != 0
-            )";
-$statement = DBManager::get()->prepare($query);
-$statement->bindValue(':provider', 'Studip\\Activity\\ForumProvider');
-$statement->execute();
-
-printf(
-    "%u forum post activities were anonymized\n",
-    $statement->rowCount()
-);
diff --git a/cli/check-help-tours.php b/cli/check-help-tours.php
deleted file mode 100755
index e028621285c1bacb44d37e70a2de4a23ab314ce1..0000000000000000000000000000000000000000
--- a/cli/check-help-tours.php
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script will check whether the help tours steps are still valid
- * regarding the controllers and actions.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- */
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-require_once __DIR__ . '/../config/config_local.inc.php';
-
-foreach (HelpTour::findBySQL('1 ORDER BY name ASC') as $tour) {
-    if (!$tour->settings->active) {
-        continue;
-    }
-
-    $errors = [];
-    foreach ($tour->steps->orderBy('step ASC') as $step) {
-        try {
-            if (strpos($step->route, 'plugins.php') === 0) {
-                $result = PluginEngine::routeRequest(substr($step->route, strlen('plugins.php') + 1));
-
-                // retrieve corresponding plugin info
-                $plugin_manager = PluginManager::getInstance();
-                $plugin_info = $plugin_manager->getPluginInfo($result[0]);
-
-                $file = implode('/', [
-//                    $GLOBALS['ABSOLUTE_PATH_STUDIP'],
-                    Config::get()->PLUGINS_PATH,
-                    $plugin_info['path'],
-                    $plugin_info['class'],
-                ]);
-
-                if (file_exists($file . '.php')) {
-                    $file .= '.php';
-                } elseif (file_exists($file . '.class.php')) {
-                    $file .= '.class.php';
-                } else {
-                    throw new Exception();
-                }
-                require_once $file;
-                $plugin = new $plugin_info['class'];
-
-                if ($result[1]) {
-                    $dispatcher = new Trails_Dispatcher(
-                        $GLOBALS['ABSOLUTE_PATH_STUDIP'] . $plugin->getPluginPath(),
-                        rtrim(PluginEngine::getLink($plugin, [], null, true), '/'),
-                        'index'
-                    );
-                    $dispatcher->current_plugin = $plugin;
-                    $parsed = $dispatcher->parse($result[1]);
-                    $controller = $dispatcher->load_controller($parsed[0]);
-                    if ($parsed[1] && !$controller->has_action($parsed[1])) {
-                        throw new Exception();
-                    }
-                }
-            } elseif (strpos($step->route, 'dispatch.php') === 0) {
-                $dispatcher = new StudipDispatcher();
-                $parsed = $dispatcher->parse(substr($step->route, strlen('dispatch.php') + 1));
-                $controller = $dispatcher->load_controller($parsed[0]);
-                if ($parsed[1] && !$controller->has_action($parsed[1])) {
-                    throw new Exception();
-                }
-            } elseif (!file_exists("{$GLOBALS['ABSOLUTE_PATH_STUDIP']}{$step->route}")) {
-                throw new Exception();
-            }
-        } catch (Exception $e) {
-            $errors[$step->step] = $step->route;
-        }
-    }
-
-    if ($errors) {
-        $type = ucfirst($tour->type);
-        echo "{$type} '{$tour->name}' has errors in the following steps:\n";
-        foreach ($errors as $step => $route) {
-            echo "- Step {$step}: {$route}\n";
-        }
-    }
-}
diff --git a/cli/cleanup_admission_rules.php b/cli/cleanup_admission_rules.php
deleted file mode 100755
index 49998ff1e77d995e8bcd9b8a9af0a75d0a9241d0..0000000000000000000000000000000000000000
--- a/cli/cleanup_admission_rules.php
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * cleanup_admission_rules.php
- *
- * deletes entries in %admissions tables
- * which were orphaned by BIEST #6617
- *
- * @author    André Noack <noack@data-quest.de>
- * @license   GPL2 or any later version
- * @copyright Stud.IP Core Group
- */
-require_once 'studip_cli_env.inc.php';
-require_once 'lib/classes/admission/CourseSet.class.php';
-
-$sql = "SELECT * FROM
-(
-SELECT rule_id,'ConditionalAdmission' as class FROM `conditionaladmissions`
-UNION
-SELECT rule_id,'CourseMemberAdmission' as class FROM `coursememberadmissions`
-UNION
-SELECT rule_id,'LimitedAdmission' as class FROM limitedadmissions
-UNION
-SELECT rule_id,'LockedAdmission' as class FROM lockedadmissions
-UNION
-SELECT rule_id,'ParticipantRestrictedAdmission' as class FROM participantrestrictedadmissions
-UNION
-SELECT rule_id,'PasswordAdmission' as class FROM passwordadmissions
-UNION
-SELECT rule_id,'TimedAdmission' as class FROM timedadmissions
-) a
-LEFT JOIN courseset_rule USING(rule_id) WHERE set_id IS NULL";
-
-$foo = new CourseSet();
-$c1 = $c2 = 0;
-DBManager::get()
-->fetchAll($sql, null, function ($data) use (&$c1,&$c2) {
-        $c1++;
-        if (class_exists($data['class'])) {
-            $rule = new $data['class']($data['rule_id']);
-            if ($rule->getId() === $data['rule_id']) {
-                echo 'deleting: ' . $rule->getName() . ' with id: ' . $rule->getId() . chr(10);
-                $c2++;
-                $rule->delete();
-            }
-        }
-}
-);
-printf("found: %s deleted: %s \n", $c1,$c2);
diff --git a/cli/compatibility-rules/studip-4.0.php b/cli/compatibility-rules/studip-4.0.php
deleted file mode 100644
index 70475b838c502a63ac41d6fbe697fb54277aa534..0000000000000000000000000000000000000000
--- a/cli/compatibility-rules/studip-4.0.php
+++ /dev/null
@@ -1,179 +0,0 @@
-<?php
-// "Rules"/definitions for critical changes in 4.0
-return [
-    'cssClassSwitcher' => 'Remove completely, use #{yellow:<table class="default">} instead.',
-    '$csssw' => '[#{cyan:cssClassSwitcher}] Remove completely, use #{yellow:<table class="default">} instead.',
-
-    'DBMigration' => 'Use #{yellow:Migration} instead',
-
-    'Request::removeMagicQuotes()' => 'Remove completely since magic quotes are removed from php',
-
-    'base_without_infobox' => 'Use #{yellow:layouts/base.php} instead.',
-    'deprecated_tabs_layout' => 'Don\'t use this. Use the global layout #{yellow:layouts/base.php} and #{yellow:Navigation} instead.',
-    'setInfoBoxImage' => 'Replace with #{yellow:Sidebar}',
-    'addToInfobox'    => 'Replace with #{yellow:Sidebar}',
-    'InfoboxElement'  => 'Replace with appropriate #{yellow:Sidebar} element',
-    'InfoboxWidget'   => 'Replace with appropriate #{yellow:Sidebar} widget',
-
-    'details.php' => 'Link to #{yellow:dispatch.php/course/details} instead',
-    'institut_main.php' => 'Link to #{yellow:dispatch.php/institute/overview} instead',
-    'meine_seminare.php' => 'Link to #{yellow:dispatch.php/my_courses} instead',
-    'sms_box.php' => 'Link to #{yellow:dispatch.php/messages/overview} or #{yellow:dispatch.php/messages/sent}  instead',
-    'sms_send.php' => 'Link to #{yellow:dispatch.php/messages/write} instead',
-
-    'get_global_perm' => 'Use #{yellow:$GLOBALS[\'perm\']->get_perm()} instead',
-    'log_event(' => 'Use #{yellow:StudipLog::log()} instead',
-    '->removeOutRangedSingleDates' => 'Use #{yellow:SeminarCycleDate::removeOutRangedSingleDates} instead',
-
-    'HolidayData' => 'Use class #{yellow:SemesterHoliday} instead',
-
-    'CourseTopic::createFolder' => 'Use #{yellow:CourseTopic::connectWithDocumentFolder()} instead',
-    'SimpleORMap::haveData' => 'Use #{yellow:SimpleORMap::isDirty()} or #{yellow:SimpleORMap::isNew()} instead',
-    'Seminar::getMetaDateType' => 'Don\'t use this!',
-    'UserConfig::setUserId' => 'Don\'t use this. #{yellow:Set the user via the constructor}.',
-
-    'StudIPTemplateEngine' => 'Time to refactor your plugin.',
-    'AbstractStudIPAdministrationPlugin' => 'Time to refactor your plugin.',
-    'AbstractStudIPCorePlugin' => 'Time to refactor your plugin.',
-    'AbstractStudIPHomepagePlugin' => 'Time to refactor your plugin.',
-    'AbstractStudIPLegacyPlugin' => 'Time to refactor your plugin.',
-    'AbstractStudIPPortalPlugin' => 'Time to refactor your plugin.',
-    'AbstractStudIPStandardPlugin' => 'Time to refactor your plugin.',
-    'AbstractStudIPSystemPlugin' => 'Time to refactor your plugin.',
-    'new Permission(' => 'Time to refactor your plugin.',
-    'Permission::' => 'Time to refactor your plugin.',
-    'PluginNavigation' => 'Time to refactor your plugin.',
-    'new StudIPUser(' => 'Time to refactor your plugin.',
-    'StudIPUser::' => 'Time to refactor your plugin.',
-    'StudipPluginNavigation' => 'Time to refactor your plugin.',
-    'getLinkToAdministrationPlugin' => 'Time to refactor your plugin.',
-    'getCurrentPluginId' => 'Time to refactor your plugin.',
-    'saveToSession' => 'Time to refactor your plugin.',
-    'getValueFromSession' => 'Time to refactor your plugin.',
-
-    'ContainerTable'   => false,
-    'DbCrossTableView' => false,
-    'DbPermissions'    => false,
-
-    'pclzip' => 'Use #{yellow:Studip\\ZipArchive} instead',
-    'get_global_visibility_by_id' => 'Use #{yellow:User::visible} attribute instead',
-
-    'getSeminarRoomRequest' => 'Use #{yellow:RoomRequest} model instead',
-    'getDateRoomRequest' => 'Use #{yellow:RoomRequest} model instead',
-
-    'ldate' => 'Use PHP\'s #{yellow:date()} or #{yellow:strftime()} function instead',
-    'day_diff' => 'Use PHP\'s #{yellow:DateTime::diff()} method instead',
-    'get_day_name' => 'Use PHP\'s #{yellow:strftime()} function with #{yellow:parameter \'%A\'} instead',
-    'wday(' => 'Use #{strftime("%a")} or #{strftime("%A")} instead',
-
-    'get_ampel_state' => false,
-    'get_ampel_write' => false,
-    'get_ampel_read' => false,
-    'localePictureUrl' => false,
-    'localeUrl' => false,
-    'isDatesMultiSem' => false,
-    'getMetadateCorrespondingDates' => false,
-    'getCorrespondingMetadates' => false,
-    'create_year_view' => false,
-    'javascript_hover_year' => false,
-    'js_hover' => false,
-    'info_icons' => false,
-
-    'get_message_attachments' => 'Use #{yellow:Message::attachments} attribute instead',
-    'view_turnus' => 'Use #{yellow:Seminar::getFormattedTurnus()} instead',
-
-    'AddNewStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'CheckSelfAssign' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'CheckSelfAssignAll' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'CheckAssignRights' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'SetSelfAssignAll' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'SetSelfAssignExclusive' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'EditStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'MovePersonPosition' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'SortPersonInAfter' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'SortStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'SubSortStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'resortStatusgruppeByRangeId' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'SwapStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'CheckStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'GetRangeOfStatusgruppe' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'GetGroupsByCourseAndUser' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'getOptionsOfStGroups' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'setOptionsOfStGroup' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'GetStatusgruppeLimit' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'CheckStatusgruppeFolder' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'CheckStatusgruppeMultipleAssigns' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'sortStatusgruppeByName' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'getPersons(' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'getSearchResults(' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-    'setExternDefaultForUser' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-
-    'GetStatusgruppeName' => 'Use #{yellow:Statusgruppen::find($id)->name} instead',
-    'GetStatusgruppenForUser' => 'Use class #{yellow:Statusgruppe} or model #{yellow:Statusgruppen} instead (yupp, this is still pretty fucked up).',
-
-    'get_global_visibility_by_id' => 'Use #{yellow:User::find($id)->visible} instead',
-    'get_global_visibility_by_username' => 'Use #{yellow:User::findByUsername($username)->visible} instead',
-
-    'get_local_visibility_by_username' => false,
-    'get_homepage_element_visibility' => false,
-    'set_homepage_element_visibility' => false,
-    'checkVisibility' => 'Use #{yellow:Visibility::verify($param, $this->current_user->user_id)} instead',
-
-    'InsertPersonStatusgruppe' => 'Use #{Statusgruppen::addUser()} instead',
-    'RemovePersonStatusgruppe(' => 'Use #{yellow:Statusgruppen::find($group_id)->removeUser($user_id)} instead',
-    'RemovePersonStatusgruppeComplete' => 'Use #{yellow:Statusgruppen::find($group_id)->removeUser($user_id, true)} instead. Maybe you will need to do this on a collection of groups for a course or institute.',
-    'RemovePersonFromAllStatusgruppen' => 'Use #{yellow:StatusgruppeUser::deleteBySQL("user_id = ?", [$user_id])} instead.',
-    'DeleteAllStatusgruppen' => 'Use #{yellow:Statusgruppen::deleteBySQL("range_id = ?", [$id]);} instead',
-    'DeleteStatusgruppe' => 'Use #{yellow:Statusgruppen::delete()} - or #{yellow:Statusgruppen::remove()} if you want to keep the child groups.',
-    'moveStatusgruppe' => false,
-    'CheckUserStatusgruppe' => 'Use #{yellow:StatusgruppeUser::exists([$group_id, $user_id])} instead.',
-    'CountMembersStatusgruppen' => false,
-    'CountMembersPerStatusgruppe' => false,
-    'MakeDatafieldsDefault' => 'No longer neccessary.',
-    'MakeUniqueStatusgruppeID' => 'No longer neccessary. SORM will create ids for you.',
-    'GetAllSelected' => 'Use #{yellow:Statusgruppen::findAllByRangeId()} instead.',
-    'getStatusgruppenIDS' => 'Use #{yellow:Statusgruppen::findByRange_id()} instead.',
-    'getAllStatusgruppenIDS' => 'Use #{yellow:Statusgruppen::findAllByRangeId()} instead.',
-    'getPersonsForRole' => 'Use #{yellow::Statusgruppen::members} instead.',
-    'isVatherDaughterRelation' => false,
-    'SetSelfAssign(' => false,
-    'getExternDefaultForUser' => 'Use #{yellow:InstituteMember::getDefaultInstituteIdForUser($user_id)} instead.',
-    'checkExternDefaultForUser' => 'Use #{yellow:InstituteMember::ensureDefaultInstituteIdForUser($user_id)} instead.',
-    'getAllChildIDs' => false,
-    'getKingsInformations' => 'Use #{yellow:User} model instead',
-
-    'AutoInsert::existSeminars' => false,
-    'new ZebraTable' => 'No longer neccessary. Use #{table.default} instead.',
-    'new Table' => 'No longer neccessary. Use #{table.default} instead.',
-
-    //old datei.inc.php and visual.inc.php functions:
-    'createSelectedZip' => 'Removed. Use #{yellow:FileArchiveManager::createArchiveFromFileRefs} instead.',
-    'create_zip_from_directory' => 'Removed(?). Use #{yellow:FileArchiveManager::createArchiveFromPhysicalFolder} instead.',
-    'getFileExtension' => 'Removed. Use PHP\'s built-in #{yellow:pathinfo($filename, PATHINFO_EXTENSION)} instead.',
-    'get_icon_for_mimetype' => 'Removed. Use #{yellow:FileManager::getIconNameForMimeType} instead.',
-    'get_upload_file_path' => 'Removed. Use #{yellow:File->getPath()} instead.',
-    'GetDownloadLink' => 'Removed. Use one of the following alternatives instead: #{yellow:FileRef->getDownloadURL()}, #{yellow:FileManager::getDownloadLinkForArchivedCourse}, #{yellow:FileManager::getDownloadLinkForTemporaryFile} or #{yellow:FileManager::getDownloadURLForTemporaryFile}',
-    'prepareFilename' => 'Removed. Use #{yellow:FileManager::cleanFileName} instead.',
-    'GetFileIcon' => 'Removed. Use #{yellow:FileManager::getIconNameForMimeType} instead.',
-    'parse_link' => 'Removed. Use #{yellow:FileManager::fetchURLMetadata} instead.',
-    'unzip_file' => 'Removed. Use #{yellow:Studip\ZipArchive::extractToPath} or #yellow:Studip\ZipArchive::test} instead.',
-    'datei.inc.php' => 'Removed. Use methods in functions.inc.php, FileManager, FileArchiveManager, FileRef, File or FolderType instead.',
-    'TrackAccess' => 'Removed(?). Use {yellow:FileRef::incrementDownloadCounter}',
-    //StudipDocument and related classes:
-    'StudipDocument(' => 'Removed(?). Use class #{yellow:FileRef} instead.',
-    'DocumentFolder(' => 'Removed(?). Use class #{yellow:Folder} instead.',
-    'StudipDocumentTree(' => 'Removed(?). Use class #{yellow:Folder} or #{yellow:FolderType} instead.',
-    'WysiwygDocument' => 'Deprecated/To be removed. Use class #{yellow:FileRef} in conjunction with a #{yellow:FolderType} implementation instead.',
-
-    'ZIP_USE_INTERNAL' => 'Removed. Please avoid querying the value of this configuration variable!',
-    'ZIP_PATH' => 'Removed. Please avoid querying the value of this configuration variable!',
-    'ZIP_OPTIONS' => 'Removed. Please avoid querying the value of this configuration variable!',
-    'UNZIP_PATH' => 'Removed. Please avoid querying the value of this configuration variable!',
-
-    'RuleAdministrationModel::getAdmissionRuleTypes' => 'Use #{yellow:AdmissionRule::getAvailableAdmissionRules(false)} instead.',
-    'SessSemName' => 'Use class #{yellow:Context} instead',
-    '_SESSION["SessionSeminar"]' => 'Use class #{yellow:Context} instead',
-    '_SESSION[\'SessionSeminar\']' => 'Use class #{yellow:Context} instead',
-
-    'Statusgruppe(' => 'Removed(?). Use class #{yellow:Statusgruppen} instead.',
-];
diff --git a/cli/compatibility-rules/studip-4.2.php b/cli/compatibility-rules/studip-4.2.php
deleted file mode 100644
index 4420e9f710c3c1baba6e5f07beb746386ea93ad4..0000000000000000000000000000000000000000
--- a/cli/compatibility-rules/studip-4.2.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-// "Rules"/definitions for critical changes in 4.2
-return [
-    'get_perm' => 'Use the #{yellow:CourseMember} or #{yellow:InstitutMember} model instead.',
-    'get_vorname' => 'Use #{yellow:User::find($id)->vorname} instead',
-    'get_nachname' => 'Use #{yellow:User::find($id)->nachname} instead',
-    'get_range_tree_path' => false,
-    'get_seminar_dozent' => 'Use #{yellow:Course::find($id)->getMembersWithStatus(\'dozent\')} instead.',
-    'get_seminar_tutor' => 'Use #{yellow:Course::find($id)->getMembersWithStatus(\'tutor\')} instead.',
-    'get_seminar_sem_tree_entries' => false,
-    'get_seminars_users' => 'Use #{yellow:CourseMember::findByUser($user_id)} instead to aquire all courses.',
-    'remove_magic_quotes' => false,
-    'text_excerpt' => false,
-    'check_group_new' => false,
-    'insertNewSemester' => 'Use the #{yellow:Semester} model instead.',
-    'updateExistingSemester' => 'Use the #{yellow:Semester} model instead.',
-];
diff --git a/cli/compatibility-rules/studip-4.4.php b/cli/compatibility-rules/studip-4.4.php
deleted file mode 100644
index 48e5165cc8e8f35b40ee94473c091e596770df20..0000000000000000000000000000000000000000
--- a/cli/compatibility-rules/studip-4.4.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-// "Rules"/definitions for critical changes in 4.4
-return [
-    'Token::is_valid' => 'Use #{yellow:Token::isValid($token, $user_id)} instead.',
-    'Token::generate' => 'Use #{yellow:Token::create($duration = 30, $user_id = null)} instead.',
-];
diff --git a/cli/compatibility-rules/studip-5.0.php b/cli/compatibility-rules/studip-5.0.php
deleted file mode 100644
index af8a70e101b732d0c63ebc0dd56aeee29597de1e..0000000000000000000000000000000000000000
--- a/cli/compatibility-rules/studip-5.0.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-// "Rules"/definitions for critical changes in 5.0
-return [
-    // https://develop.studip.de/trac/ticket/11250
-    'userMayAccessRange' => '#{yellow:Changed} - Use #{yellow:isAccessibleToUser} instead',
-    'userMayEditRange' => '#{yellow:Changed} - Use #{yellow:isEditableByUser} instead',
-    'userMayAdministerRange' => '#{red:Removed}',
-
-    // UTF8-Encode/Decode legacy functions
-    'studip_utf8encode' => '#{red:Removed} - Use utf8_encode().',
-    'studip_utf8decode' => '#{red:Removed} - Use utf8_decode().',
-
-    // JSON encode/decode legacy functions
-    'studip_json_decode' => '#{red:Deprecated} - Use json_decode() and pay attention to the second parameter.',
-    'studip_json_encode' => '#{red:Deprecated} - Use json_encode().',
-
-    // https://develop.studip.de/trac/ticket/10806
-    'SemesterData' => '#{red:Removed} - Use #{yellow:Semester model} instead',
-
-    // https://develop.studip.de/trac/ticket/10786
-    'StatusgroupsModel' => '#{red:Removed} - Use #{yellow:Statusgruppen model} instead',
-
-    // https://develop.studip.de/trac/ticket/10796
-    'StudipNullCache' => '#{red:Removed} - Use #{yellow:StudipMemoryCache} instead',
-
-    // https://develop.studip.de/trac/ticket/10838
-    'getDeputies' => '#{red:Removed} - Use #{yellow:Deputy::findDeputies()} instead',
-    'getDeputyBosses' => '#{red:Removed} - Use #{yellow:Deputy::findDeputyBosses()} instead',
-    '/(?<!Deputy::)addDeputy/' => '#{red:Removed} - Use #{yellow:Deputy::addDeputy()} instead',
-    '/deleteDeputy(?=\()/' => '#{red:Removed} - Use #{yellow:Deputy model} instead',
-    'deleteAllDeputies' => '#{red:Removed} - Use #{yellow:Deputy::deleteByRange_id} instead',
-    '/(?<!Deputy::)isDeputy/' => '#{red:Removed} - Use #{yellow:Deputy::isDeputy()} instead',
-    'setDeputyHomepageRights' => '#{red:Removed} - Use #{yellow:Deputy model} instead',
-    'getValidDeputyPerms' => '#{red:Removed} - Use #{yellow:Deputy::getValidPerms()} instead',
-    'isDefaultDeputyActivated' => '#{red:Removed} - Use #{yellow:Deputy::isActivated()} instead',
-    'getMyDeputySeminarsQuery' => '#{red:Removed} - Use #{yellow:Deputy::getMySeminarsQuery()} instead',
-    'isDeputyEditAboutActivated' => '#{red:Removed} - Use #{yellow:Deputy::isEditActivated()} instead',
-
-    // https://develop.studip.de/trac/ticket/10870
-    'get_config' => '#{red:Deprecated} - Use #{yellow:Config::get()} instead.',
-
-    // https://develop.studip.de/trac/ticket/10919
-    'RESTAPI\\RouteMap' => '#{red:Deprecated} - Use the #{yellow:JSONAPI} instead.',
-
-    // https://develop.studip.de/trac/ticket/10878
-    'Leafo\\ScssPhp' => 'Library was replaced by #{yellow:scssphp/scssphp}',
-    'sfYamlParser'   => 'Library was replaced by #{yellow:symfony/yaml}',
-    'DocBlock::of'   => 'Library was replaced by #{yellow:gossi/docblock}',
-
-    'vendor/idna_convert' => 'Remove include/require. Will be autoloaded.',
-    'vendor/php-htmldiff' => 'Remove include/require. Will be autoloaded.',
-    'vendor/HTMLPurifier' => 'Remove include/require. Will be autoloaded.',
-    'vendor/phplot'       => 'Remove include/require. Will be autoloaded.',
-    'vendor/phpCAS'       => 'Remove include/require. Will be autoloaded.',
-    'vendor/phpxmlrpc'    => 'Remove include/require. Will be autoloaded.',
-
-    // https://develop.studip.de/trac/ticket/10964
-    'periodicalPushData'                           => '#{red:Removed} - Use #{yellow:STUDIP.JSUpdater.register()} instead',
-    '/UpdateInformtion::setInformation\(.+\..+\)/' => '#{red:Removed} - Use #{yellow:STUDIP.JSUpdater.register()} instead',
-];
diff --git a/cli/create_table_schemes.php b/cli/create_table_schemes.php
deleted file mode 100755
index a7f557eabc1dcd9bae93e5afd8ee99613140260b..0000000000000000000000000000000000000000
--- a/cli/create_table_schemes.php
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env php
-<?php
-# Lifter007: TODO
-# Lifter003: TODO
-/**
-* create_table_schemes.php
-* 
-* 
-* 
-*
-* @author       André Noack <noack@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de>
-* @access       public
-*/
-// +---------------------------------------------------------------------------+
-// This file is part of Stud.IP
-// create_table_schemes.php
-// 
-// Copyright (C) 2006 André Noack <noack@data-quest.de>,
-// Suchi & Berg GmbH <info@data-quest.de>
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-require_once dirname(__FILE__) . '/studip_cli_env.inc.php';
-exec("grep -l 'extends SimpleORMap' $STUDIP_BASE_PATH/lib/classes/*.class.php", $output, $ok);
-if(!$ok ){
-    fwrite(STDOUT, "<?php\n//copy to \$STUDIP_BASE_PATH/lib/dbviews/table_schemes.inc.php\n//generated ". date('r') ."\n");
-    foreach($output as $line){
-        require_once $line;
-        list($classname,,) = explode('.',basename($line));
-        $o = new $classname();
-        fwrite(STDOUT, $o->exportScheme());
-    }
-    fwrite(STDOUT, "?>");
-}
-
-?>
\ No newline at end of file
diff --git a/cli/cronjob-worker.php b/cli/cronjob-worker.php
deleted file mode 100755
index 15f6e2ed04d7fc8a12867ea80472c6437e5e9b82..0000000000000000000000000000000000000000
--- a/cli/cronjob-worker.php
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * cronjob-worker - Worker process for the cronjobs
- *
- * @author      Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category    Stud.IP
- * @since       2.4
- */
-
-// +---------------------------------------------------------------------------+
-// This file is part of Stud.IP
-// cronjob-worker.php
-//
-// Copyright (C) 2013 Jan-Hendrik Willms <tleilax+studip@gmail.com>
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-
-    require_once 'studip_cli_env.inc.php';
-
-    CronjobScheduler::getInstance()->run();
\ No newline at end of file
diff --git a/cli/cronjobs.php b/cli/cronjobs.php
deleted file mode 100755
index 217c6643197386e75a6050e7aff7765103ca76bd..0000000000000000000000000000000000000000
--- a/cli/cronjobs.php
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
-* cronjobs - Helper script for the cronjobs
-*
-* @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
-* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
-* @category Stud.IP
-* @since 3.1
-* @todo Parameter handling!
-*/
-
-require_once 'studip_cli_env.inc.php';
-
-$argc = $_SERVER['argc'];
-$argv = $_SERVER['argv'];
-
-$opts = getopt('hl', ['help', 'list']);
-
-if (isset($opts['l']) || isset($opts['list'])) {
-    $tasks = CronjobTask::findBySql('1');
-    foreach ($tasks as $task) {
-        $description = call_user_func([$task->class, 'getDescription']);
-        fwrite(STDOUT, sprintf('%s %s' . PHP_EOL, $task->id, $description));
-    }
-    exit(0);
-}
-
-if ($argc < 2 || isset($opts['h']) || isset($opts['help'])) {
-    fwrite(STDOUT,'Usage: ' . basename(__FILE__) . ' [--help] [--list] <task_id> [last_result]' . PHP_EOL);
-    exit(0);
-}
-
-
-$id = $_SERVER['argv'][1];
-$last_result = $argc > 2 ? $_SERVER['argv'][2] : null;
-$task = CronjobTask::find($id);
-if (!$task) {
-    fwrite(STDOUT, 'Unknown task id' . PHP_EOL);
-    exit(0);
-}
-
-if (!file_exists($GLOBALS['STUDIP_BASE_PATH'] . '/' . $task->filename)) {
-    fwrite(STDOUT, 'Invalid task, unknown filename "' . $task->filename . '"' . PHP_EOL);
-    exit(0);
-}
-
-require_once $task->filename;
-if (!class_exists($task->class)) {
-    fwrite(STDOUT, 'Invalid task, unknown class "' . $task->class . '"' . PHP_EOL);
-    exit(0);
-}
-
-$task->engage($last_result);
diff --git a/cli/describe_models.php b/cli/describe_models.php
deleted file mode 100755
index ebe0db396bd5e14cddb7c166dbf52cc63e0806ef..0000000000000000000000000000000000000000
--- a/cli/describe_models.php
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once 'studip_cli_env.inc.php';
-
-$dir = new FilesystemIterator($STUDIP_BASE_PATH . '/lib/models');
-foreach ($dir as $fileinfo) {
-    $class = mb_strstr($fileinfo->getFilename(), '.', true);
-    if (!in_array($class, words('SimpleCollection SimpleORMap SimpleORMapCollection StudipArrayObject')) && class_exists($class)) {
-        echo $class . "\n";
-        $model = new $class;
-        $meta = $model->getTableMetaData();
-        $props = [];
-        foreach ($meta['fields'] as $field => $info) {
-            $name = mb_strtolower($field);
-            $props[$name] = '@property string ' . $name;
-            $props[$name] .= ' database column';
-            if ($alias = array_search($name, $meta['alias_fields'])) {
-                $props[$alias] = '@property string ' . $alias;
-                $props[$alias] .= ' alias column for ' . $name;
-            }
-        }
-        foreach ($meta['additional_fields'] as $field => $info) {
-            $name = mb_strtolower($field);
-            $props[$name] = '@property string ' . $name;
-            $props[$name] .= ' computed column';
-            $getter = isset($info['get']) || method_exists($model, 'get' . $name);
-            $setter = isset($info['set']) || method_exists($model, 'set' . $name);
-           
-            if ($setter && $getter) {
-                $props[$name] .= ' read/write';
-            } else if ($setter) {
-                $props[$name] .= ' read only';
-            }
-        }
-        foreach ($meta['relations'] as $relation) {
-            $options = $model->getRelationOptions($relation);
-            $props[$relation] = '@property ';
-            if ($options['type'] === 'has_many' ||
-            $options['type'] === 'has_and_belongs_to_many') {
-                $props[$relation] .= 'SimpleORMapCollection';    
-            } else {
-                $props[$relation] .= $options['class_name'];
-            }
-            $props[$relation] .= ' ' . $relation;
-            $props[$relation] .= ' ' . $options['type'] . ' ' . $options['class_name'];
-        }
-        $props = array_map(function($p) {return ' * ' . $p . "\n";}, $props);
-        $file = file($fileinfo->getPathname());
-        foreach ($file as $n => $line) if (mb_strpos($line, 'class') === 0) break;
-        if ($n < count($file)) {
-            $classstart = $n;
-            $propend = null;
-            $propstart = null;
-            $docend = null;
-            for ($n; $n >= 0; --$n) {
-                if (!isset($docend) && mb_strpos($file[$n], ' */') === 0) $docend = $n;
-                if (!isset($propend) && mb_strpos($file[$n], ' * @property') === 0) $propend = $n;
-                if (isset($propend) && mb_strpos($file[$n], ' * @property') === 0) $propstart = $n;
-            }
-            if (isset($docend)) {
-                if (isset($propstart)) {
-                    array_splice($file, $propstart, $propend-$propstart+1, $props);
-                } else {
-                    array_splice($file, $docend, 0, $props);
-                }
-                $ok = file_put_contents($fileinfo->getPathname(), join('', array_map(function($l) {return rtrim($l, "\r\n") . PHP_EOL;}, $file)));
-                if ($ok) echo $fileinfo->getPathname() . " written \n";
-                else echo $fileinfo->getPathname() . " not writable \n";
-            } else {
-                echo 'no docblock found in ' . $fileinfo->getPathname() . chr(10);
-            }
-            
-        }
-    }
-}
diff --git a/cli/dump_studip.php b/cli/dump_studip.php
deleted file mode 100755
index 52ce0c1d33e93d068d58ce146b17c5806ad7241c..0000000000000000000000000000000000000000
--- a/cli/dump_studip.php
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
-* dump_studip.php
-*
-*
-*
-*
-* @author       André Noack <noack@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de>
-* @access       public
-*/
-// +---------------------------------------------------------------------------+
-// This file is part of Stud.IP
-// dump_studip.php
-//
-// Copyright (C) 2011 André Noack <noack@data-quest.de>
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-require_once 'studip_cli_env.inc.php';
-
-function exec_or_die($cmd) {
-    exec($cmd . ' 2>&1',$output,$ok);
-    if ($ok > 0) {
-        fwrite(STDOUT,join("\n", array_merge([$cmd], $output)) . "\n");
-        exit(1);
-    }
-}
-
-$dump_dir = $_SERVER['argv'][1] ? realpath($_SERVER['argv'][1]) : null;
-$dump_only = $_SERVER['argv'][2];
-
-if (!$dump_dir) {
-    fwrite(STDOUT,'Usage: ' . basename(__FILE__) . ' PATH [db|base|data]' .chr(10).'Dump all without second parameter.'.chr(10));
-    exit(0);
-}
-if (!is_writeable($dump_dir)) {
-    trigger_error('Directory: ' . $dump_dir . ' is not writeable!', E_USER_ERROR);
-}
-
-$today = date("Ymd");
-$prefix = Config::get()->STUDIP_INSTALLATION_ID ? Config::get()->STUDIP_INSTALLATION_ID : 'studip';
-if (!$dump_only || $dump_only == 'db') {
-    $dump_db_dir = $dump_dir . '/db-' . $today;
-    if (!is_dir($dump_db_dir)) {
-        mkdir($dump_db_dir);
-    }
-    foreach(DBManager::get()->query("SHOW TABLES") as $tables) {
-        $table = $tables[0];
-        $dump_table = $dump_db_dir . '/' . $table . '-' . $today . '.sql';
-        fwrite(STDOUT, 'Dumping database table ' . $table . chr(10));
-        exec_or_die("mysqldump -u$DB_STUDIP_USER -h$DB_STUDIP_HOST -p$DB_STUDIP_PASSWORD $DB_STUDIP_DATABASE $table > $dump_table");
-    }
-    $dump_db = $dump_dir . '/' . $prefix . '-DB-' . $today . '.tar.gz';
-    fwrite(STDOUT, 'Packing database to ' . $dump_db . chr(10));
-    exec_or_die("cd $dump_db_dir && tar -czf $dump_db *");
-    exec_or_die("rm -rf $dump_db_dir");
-}
-if (!$dump_only || $dump_only == 'base') {
-    $dumb_studip = $dump_dir . '/' . $prefix . '-BASE-' . $today . '.tar.gz';
-    $base_path = realpath($STUDIP_BASE_PATH);
-    if (!$base_path) {
-        trigger_error('Stud.IP directory not found!', E_USER_ERROR);
-    }
-    fwrite(STDOUT, 'Dumping Stud.IP directory to ' . $dumb_studip . chr(10));
-    exec_or_die("cd $base_path && tar -czf $dumb_studip --exclude 'data/*' .");
-}
-if (!$dump_only || $dump_only == 'data') {
-    $data_path = realpath($UPLOAD_PATH . '/../');
-    if ($data_path) {
-        $dumb_data = $dump_dir . '/' . $prefix . '-DATA-' . $today . '.tar.gz';
-        fwrite(STDOUT, 'Dumping data directory to ' . $dumb_data . chr(10));
-        exec_or_die("cd $data_path && tar -czf $dumb_data .");
-    }
-}
-exit(0);
diff --git a/cli/extract-js-localizations.php b/cli/extract-js-localizations.php
deleted file mode 100755
index 70d14a9a0417245f762d0c941b3ef13ca8cbe8cc..0000000000000000000000000000000000000000
--- a/cli/extract-js-localizations.php
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * extract-js-localizations.php
- *
- * Exports all strings from js into app/views/localizations/show.php so
- * they can be translated as well.
- *
- * @author    Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license   GPL2 or any later version
- * @copyright Stud.IP Core Group
- * @since     3.1
- */
-
-require 'studip_cli_env.inc.php';
-
-/**
- * Determines whether the file should be skipped depending on an exclude list
- * with an additional include list. This allows inclusion inside of previously
- * excluded entries. We need this for the assets directory.
- * Furthermore, the file is checked against a list of mime types to include.
- *
- * @param String $filename Adjusted filename (stripped to path inside trunk)
- * @param String $realfile Actual file name (needed for mime type detection)
- * @return bool indicating whether the file should be skipped or not
- */
-function should_skip_file($filename, $realfile) {
-    $exclude = [
-        'cli/*',
-        'composer/*',
-        'config/*',
-        'data/*',
-        'db/*',
-        'doc/*',
-        'locale/*',
-        'node_modules/*',
-        'public/assets/flash*',
-        'public/assets/fonts*',
-        'public/assets/images*',
-        'public/assets/javascripts/*',
-        'public/assets/sounds*',
-        'public/assets/squeezed*',
-        'public/assets/stylesheets*',
-        'public/pictures/*',
-        'public/plugins_packages/*',
-        'test/*',
-        'tests/*',
-        'vendor/*',
-    ];
-    $include = [
-        'public/assets/javascripts/ckeditor*',
-        'public/plugins_packages/core*',
-    ];
-    $mime_types = [
-        'text/*',
-        'application/javascript',
-    ];
-
-    // Check if the file should be excluded, depending on it's path.
-    $matching_pattern = null;
-    $skip             = false;
-    foreach ($exclude as $pattern) {
-        if (fnmatch($pattern, $filename)) {
-            $matching_pattern = $pattern;
-            $skip             = true;
-            break;
-        }
-    }
-
-    // If it should be skipped in step 1, check if it matches the include
-    // patterns and no longer skip it, if it matches.
-    // Matches are only from patterns that are longer than the pattern that
-    // set the entry to be skipped. Thus it is detected if the file is in a
-    // subdirectory.
-    if ($skip) {
-        foreach ($include as $pattern) {
-            if (fnmatch($pattern, $filename) && mb_strlen($pattern) > mb_strlen($matching_pattern)) {
-                $skip = false;
-                break;
-            }
-        }
-    }
-
-    // If the file should not be skipped, check it's mime type and skip it
-    // if the mime type is not allowed.
-    if (!$skip && is_file($realfile)) {
-        $mime_type = mime_content_type($realfile);
-
-        $skip = true;
-        foreach ($mime_types as $pattern) {
-            if (fnmatch($pattern, $mime_type)) {
-                $skip = false;
-                break;
-            }
-        }
-    }
-
-    return $skip;
-}
-
-/**
- * Extract the actual text strings from a file. This will only detect single
- * line text strings. Multi line strings are just a hassle to handle in js
- * anyways.
- *
- * @param String $file Filename to extract text strings from
- * @return mixed Array with found text strings or false if no text strings
- *               were found
- */
-function extract_strings($file) {
-    $contents = file_get_contents($file);
-    $regexp   = '/(?:\'([^\']+)\'|"([^"]+)")\\.toLocaleString\\(\\s*\\)/';
-
-    if (preg_match_all($regexp, $contents, $matches, PREG_SET_ORDER)) {
-        $result = [];
-        foreach ($matches as $match) {
-            $result[] = $match[1] ?: $match[2];
-        }
-        return array_unique($result);
-    }
-
-    return false;
-}
-
-/**
- * Recursively find text strings in files in the given directory.
- * This skips invalid files.
- *
- * @param String $directory Directory to search files in
- * @param mixed  $base      Optional base directory to strip from file names,
- *                          will default to the initial passed directory.
- * @return Array Associative array with filenames as index and an array of
- *               the text strings the file contains.
- */
-function find_strings_in_dir($directory, $base = null) {
-    $result = [];
-
-    $base = rtrim($base ?: $directory, '/') . '/';
-
-    $files = glob(rtrim($directory, '/') . '/*');
-    foreach ($files as $file) {
-        $filename = str_replace($base, '', $file);
-        $is_dir   = is_dir($file);
-
-        if (should_skip_file($filename, $file)) {
-            continue;
-        }
-
-        if (is_dir($file)) {
-            $result += find_strings_in_dir($file, $base);
-        } elseif ($strings = extract_strings($file)) {
-            $result[$filename] = $strings;
-        }
-    }
-
-    return $result;
-}
-
-// Find text strings in all stud.ip files
-$occurences = find_strings_in_dir(realpath(__DIR__ . '/..'));
-
-// Remove duplicates
-$hashes = [];
-foreach ($occurences as $file => $strings) {
-    foreach ($strings as $index => $string) {
-        $hash = md5($string);
-        if (in_array($hash, $hashes)) {
-            unset($strings[$index]);
-        } else {
-            $hashes[] = $hash;
-        }
-    }
-    if (empty($strings)) {
-        unset($occurences[$file]);
-    } else {
-        $occurences[$file] = $strings;
-    }
-}
-
-// Create trails view as output
-ob_start();
-?>
-<?= '<?php' . PHP_EOL ?>
-
-$translations = array(
-<? foreach ($occurences as $file => $strings): ?>
-    // <?= $file . PHP_EOL ?>
-<? foreach ($strings as $string): ?>
-    '<?= addcslashes($string, "'") ?>' => _('<?= addcslashes($string, "'") ?>'),
-<? endforeach; ?>
-
-<? endforeach; ?>
-);
-
-?>
-<?= '<?=' ?> json_encode($translations) <?= '?>' ?>
-<?
-$view = ob_get_clean();
-
-// Write output to the corresponding file
-file_put_contents(__DIR__ . '/../app/views/localizations/show.php', $view);
-
-// Show some statistics
-printf('%u strings written to file' . PHP_EOL, array_sum(array_map('count', $occurences)));
diff --git a/cli/fix-icon-dimensions.php b/cli/fix-icon-dimensions.php
deleted file mode 100755
index a7e595afe01139661f1794bbfb2dc046afee8cb3..0000000000000000000000000000000000000000
--- a/cli/fix-icon-dimensions.php
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once __DIR__ . '/studip_cli_env.inc.php';
-
-$folder = $GLOBALS['STUDIP_BASE_PATH'] . '/public/assets/images/icons';
-$iterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS);
-$iterator = new RecursiveIteratorIterator($iterator);
-$regexp_iterator = new RegexIterator($iterator, '/\.svg$/', RecursiveRegexIterator::MATCH);
-
-foreach ($regexp_iterator as $file) {
-    $contents = file_get_contents($file);
-
-    $xml = simplexml_load_string($contents);
-    $attr = $xml->attributes();
-    if ($attr->width && $attr->height) {
-        continue;
-    }
-    $contents = str_replace('<svg ', '<svg width="16" height="16" ', $contents);
-    file_put_contents($file, $contents);
-
-    echo "Adjusted $file\n";
-}
diff --git a/cli/fix_collate.php b/cli/fix_collate.php
deleted file mode 100755
index b9b9f6737f2acc378da095cc0540c48c37ee8721..0000000000000000000000000000000000000000
--- a/cli/fix_collate.php
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * @author Witali Mik <mik@data-quest.de>
- * Script um Collation Konflikte automatisiert zu lösen
- */
-
-require_once dirname(__FILE__) . '/studip_cli_env.inc.php';
-require_once 'lib/classes/DBManager.class.php';
-require_once 'config/config_local.inc.php';
-
-$charset = 'latin1';
-$collate = 'latin1_german1_ci';
-$sql = "SELECT CONCAT('ALTER TABLE `".$DB_STUDIP_DATABASE."`.`', TABLE_NAME, '` CONVERT TO CHARACTER SET ".$charset." COLLATE ".$collate.";') as query  FROM `information_schema`.TABLES WHERE TABLE_SCHEMA='".$DB_STUDIP_DATABASE."' AND TABLE_COLLATION!='".$collate."'";
-
-$db = DBManager::get();
-
-
-$result = $db->query($sql);
-foreach($result->fetchAll(PDO::FETCH_OBJ) as $row){
-   $db->exec($row->query);
-    fwrite(STDOUT, sprintf("Execute: %s \n",$row->query));
-}
-fwrite(STDOUT, "Finished");
\ No newline at end of file
diff --git a/cli/fix_endtime_weekly_recurred_events.php b/cli/fix_endtime_weekly_recurred_events.php
deleted file mode 100755
index 7e31f2932f7c96d6b501ab7d44dc78f13b0e1ece..0000000000000000000000000000000000000000
--- a/cli/fix_endtime_weekly_recurred_events.php
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * fix_endtime_weekly_recurred_events.php
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * @author      Peter Thienel <thienel@data-quest.de>
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category    Stud.IP
- * @since       3.5
- */
-require_once __DIR__ . '/studip_cli_env.inc.php';
-
-$events = EventData::findBySQL("rtype = 'WEEKLY' AND IFNULL(count, 0) > 0");
-$cal_event = new CalendarEvent();
-$i = 0;
-foreach ($events as $event) {
-    $id = $event->getId();
-    $cal_event->event = $event;
-    $rrule = $cal_event->getRecurrence();
-    $cal_event->setRecurrence($rrule);
-    $event->expire = $cal_event->event->expire;
-    $event->setId($id);
-    $event->store();
-    $i++;
-}
-
-fwrite(STDOUT, 'Wrong end time of recurrence fixed for ' . $i . ' events.' . chr(10));
-exit(1);
diff --git a/cli/getopts.php b/cli/getopts.php
deleted file mode 100644
index dde439f8e0191a1f509c5f4c93548f30c0c19a94..0000000000000000000000000000000000000000
--- a/cli/getopts.php
+++ /dev/null
@@ -1,317 +0,0 @@
-<?php
-
-    /*
-
-        getopts by ALeX Kazik
-
-        Code: https://github.com/alexkazik/getopts
-        Docs: https://github.com/alexkazik/getopts/wiki/Documentation
-        Homepage: http://alex.kazik.de/195/getopts/
-
-        License: Creative Commons Attribution 3.0 Unported License
-        http://creativecommons.org/licenses/by/3.0/
-
-    */
-
-    function getopts($params, $args=NULL, $raw=false){
-        // check input
-        if(!is_array($params)){
-            trigger_error('Invalid params table', E_USER_ERROR);
-        }
-        if($args === NULL && is_array($_SERVER['argv'])){
-            $args = $_SERVER['argv'];
-            array_shift($args);
-        }
-        if(!is_array($args)){
-            trigger_error('Invalid args table', E_USER_ERROR);
-        }
-        if(!is_bool($raw)){
-            trigger_error('Invalid raw option', E_USER_ERROR);
-        }
-
-        // mb_substr, which returns '' in case of an empty mb_substr (usually false)
-        $mb_substr = function ($string, $start, $length = null) { // is not used, only for definition
-            $ret = call_user_func_array('mb_substr', func_get_args());
-            if ($ret === false){
-               return '';
-            } else {
-                return $ret;
-            }
-        };
-
-        // get arg (either implicit or the following)
-        $get_arg = function (&$next, &$args, &$num) { // pass by reference: num may be changed, others: performance
-            if ($next !== true) {
-                return $next;
-            } elseif ($num + 1 >= count($args)){
-                return false;
-            } else {
-                $num += 1;
-                return $args[$num];
-            }
-        };
-
-        // all types & subtypes
-        $types_subtypes = ['S' => 'stcr', 'V' => 'smar', 'O' => 'smar', 'A' => 'sr'];
-
-        // output
-        $Ores = [];
-        $Oerr = [];
-        $Oags = [];
-
-        // parsed options
-        $short = [];
-        $long = [];
-        $type = [];
-
-        // parse options
-        foreach($params AS $opt => $names){
-            if(is_string($names)){
-                $names = preg_split('/ +/', $names);
-            }
-            if(!is_array($names) || count($names) < 2){
-                trigger_error('Invalid type/name(s) to param "'.$opt.'"', E_USER_ERROR);
-            }
-
-            $ty = array_shift($names);
-            if(!is_string($ty) || mb_strlen($ty) < 1 || mb_strlen($ty) > 2){
-                trigger_error('Invalid type to param "'.$opt.'"', E_USER_ERROR);
-            }
-            $ty0 = $ty[0];
-            if(!isset($types_subtypes[$ty0])){
-                trigger_error('Invalid type to param "'.$opt.'"', E_USER_ERROR);
-            }
-            if(mb_strlen($ty) == 1){
-                $ty1 = $types_subtypes[$ty0][0];
-            }else{
-                $ty1 = $ty[1];
-                if(mb_strpos($types_subtypes[$ty0], $ty1) === false){
-                    trigger_error('Invalid type to param "'.$opt.'"', E_USER_ERROR);
-                }
-            }
-            $type[$opt] = $ty0.$ty1;
-
-            foreach($names AS $name){
-                if(!is_string($name)){
-                    trigger_error('Invalid names to param "'.$opt.'"', E_USER_ERROR);
-                }
-                if(!preg_match('!^(-)?([0-9a-zA-Z]+)$!', $name, $r)){
-                    trigger_error('Invalid name to param "'.$opt.'"', E_USER_ERROR);
-                }
-                if($r[1] == '-' || mb_strlen($r[2]) > 1){
-                    if(isset($long[$r[2]])){
-                        trigger_error('Duplicate option name "'.$r[2].'"', E_USER_ERROR);
-                    }
-                    $long[$r[2]] = $opt;
-                }else{
-                    if(isset($short[$r[2]])){
-                        trigger_error('Duplicate option name "'.$r[2].'"', E_USER_ERROR);
-                    }
-                    $short[$r[2]] = $opt;
-                }
-            }
-
-            $Ores[$opt] = [];
-        }
-
-        // parse arguments
-        for($num=0; $num<count($args); $num++){
-            $arg = $args[$num];
-
-            if($arg == '--'){
-                // end of options, copy all other args
-                $num++;
-                for(; $num<count($args); $num++){
-                    $Oags[] = $args[$num];
-                }
-                break;
-            }else if($arg == ''){
-                // empty -> skip
-                continue;
-            }else if($arg[0] != '-'){
-                // not an option -> copy to args
-                $Oags[] = $arg;
-                continue;
-            }
-
-            // this arg is an option!
-            if($arg[1] == '-'){
-                // long option
-                $p = mb_strpos($arg, '=');
-                if($p !== false){
-                    $next = $mb_substr($arg, $p+1);
-                    $arg = mb_substr($arg, 2, $p-2);
-                }else{
-                    $next = true;
-                    $arg = mb_substr($arg, 2);
-                }
-                if(!isset($long[$arg])){
-                    $Oerr[] = 'Unknown option "--'.$arg.'"';
-                }else{
-                    $opt = $long[$arg];
-                    $Earg = '--'.$arg;
-                    switch($type[$opt][0]){
-                    case 'S':
-                        $Ores[$opt][] = $next;
-                        break;
-                    case 'V':
-                        if(($val = $get_arg($next,$args,$num)) === false){
-                            $Oerr[] = 'Missing artument to option "'.$Earg.'"';
-                        }else{
-                            $Ores[$opt][] = $val;
-                        }
-                        break;
-                    case 'O':
-                        $Ores[$opt][] = $next;
-                        break;
-                    case 'A':
-                        if(($val = $get_arg($next,$args,$num)) === false){
-                            $Oerr[] = 'Missing artument to option "'.$Earg.'"';
-                        }else{
-                            $p = mb_strpos($val, '=');
-                            if($p === false){
-                                $Oerr[] = 'Malformed artument to option "'.$Earg.'" (a "=" is missing)';
-                            }else if(isset($Ores[$opt][mb_substr($val, 0, $p)])){
-                                $Oerr[] = 'Duplicate key "'.mb_substr($val, 0, $p).'" to option "'.$Earg.'"';
-                            }else{
-                                $Ores[$opt][mb_substr($val, 0, $p)] = $mb_substr($val, $p+1);
-                            }
-                        }
-                        break;
-                    }
-                }
-            }else{
-                // short option(s)
-                for($i=1; $i<mb_strlen($arg); $i++){
-                    $c = $arg[$i];
-                    $next = $mb_substr($arg, $i+1);
-                    if($next == ''){
-                        $next = true;
-                    }else if($next[0] == '='){
-                        $next = $mb_substr($next, 1);
-                    }
-                    if(!isset($short[$c])){
-                        $Oerr[] = 'Unknown option "-'.$c.'"';
-                        $i = mb_strlen($arg);
-                    }else{
-                        $opt = $short[$c];
-                        $Earg = '-'.$c;
-                        switch($type[$opt][0]){
-                        case 'S':
-                            $Ores[$opt][] = true;
-                            break;
-                        case 'V':
-                            if(($val = $get_arg($next,$args,$num)) === false){
-                                $Oerr[] = 'Missing artument to option "'.$Earg.'"';
-                            }else{
-                                $Ores[$opt][] = $val;
-                            }
-                            $i = mb_strlen($arg);
-                            break;
-                        case 'O':
-                            $Ores[$opt][] = $next;
-                            $i = mb_strlen($arg);
-                            break;
-                        case 'A':
-                            if(($val = $get_arg($next,$args,$num)) === false){
-                                $Oerr[] = 'Missing artument to option "'.$Earg.'"';
-                            }else{
-                                $p = mb_strpos($val, '=');
-                                if($p === false){
-                                    $Oerr[] = 'Malformed artument to option "'.$Earg.'" (a "=" is missing)';
-                                }else if(isset($Ores[$opt][mb_substr($val, 0, $p)])){
-                                    $Oerr[] = 'Duplicate key "'.mb_substr($val, 0, $p).'" to option "'.$Earg.'"';
-                                }else{
-                                    $Ores[$opt][mb_substr($val, 0, $p)] = $mb_substr($val, $p+1);
-                                }
-                            }
-                            $i = mb_strlen($arg);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-
-        // reformat result
-        if(!$raw){
-            foreach($Ores AS $opt => &$r){
-                switch($type[$opt]){
-                case 'Ss':
-                    $r = count($r) > 0;
-                    break;
-                case 'St':
-                    $r = (count($r) & 1) == 1;
-                    break;
-                case 'Sc':
-                    $r = count($r);
-                    break;
-
-                case 'Vs':
-                    if(count($r) == 0){
-                        // no option
-                        $r = false;
-                    }else{
-                        // pick last entry
-                        $r = array_pop($r);
-                    }
-                    break;
-
-                case 'Os':
-                    if(count($r) == 0){
-                        // no option
-                        $r = false;
-                    }else{
-                        // pick last entry; if possible last used (non true) entry
-                        do{
-                            $rr = array_pop($r);
-                        }while($rr === true && count($r) > 0);
-                        $r = $rr;
-                    }
-                    break;
-
-                case 'Vm':
-                case 'Om':
-                    if(count($r) == 0){
-                        // no option
-                        $r = false;
-                    }else{
-                        // as array
-                        // (already done)
-                    }
-                    break;
-
-                case 'Va':
-                case 'Oa':
-                    // false if none, direct (string) if only one, array otherwise
-                    if(count($r) == 0){
-                        // no option
-                        $r = false;
-                    }else if(count($r) == 1){
-                        // a single option
-                        $r = array_pop($r);
-                    }else{
-                        // as array
-                        // (already done)
-                    }
-                    break;
-
-                case 'As':
-                    // as array
-                    // (already done)
-                    break;
-
-                }
-            }
-        }
-
-        // errors?
-        if(count($Oerr) == 0){
-            $Oerr = false;
-        }
-
-        // result
-        return [$Oerr, $Ores, $Oags];
-    }
-
-?>
diff --git a/cli/help-translation-tool.php b/cli/help-translation-tool.php
deleted file mode 100755
index 6323eae69c97eee7577544eb3a21dbd79250ce21..0000000000000000000000000000000000000000
--- a/cli/help-translation-tool.php
+++ /dev/null
@@ -1,543 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * help-translation-tool.php
- *
- * Exports db data for the help content, tooltips and tours into a .po file or
- * reimports the translated strings into the db.
- *
- * Since we need to obtain the row to inssert/update the translated content,
- * this information is coded into the corresponding filename and line number.
- *
- * By using a specific range for line number, we can determine what type the
- * translated string is:
- *
- * range         | context    | location               | file  | line number
- * --------------+------------+------------------------+-------+-------------
- * 10000 - 19999 | -          | help_content.label     | route | position
- * 20000 - 29999 | content_id | help_content.content   | route | position
- * 30000 - 39999 | tour_id    | help_tours.name        | -     | version
- * 40000 - 49999 | tour_id    | help_tours.description | -     | version
- * 50000 - 59999 | tour_id    | help_tour_steps.title  | route | step
- * 60000 - 69999 | tour_id    | help_tour_steps.tip    | route | step
- * 70000 - 79999 | tooltip_id | help_tooltips.content  | route | version
- *
- * @author    Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license   GPL2 or any later version
- * @copyright Stud.IP Core Group
- * @since     3.1
- */
-
-require_once 'studip_cli_env.inc.php';
-
-define('MAX_LINE_LENGTH', 60);
-
-/**
- * Escapes a string for use in .po file.
- *
- * @param String $string String to escape
- * @return String Escaped string
- */
-function po_escape($string) {
-    return str_replace('"', '\\"', $string);
-}
-
-/**
- * Unescapes a string for use in .po file.
- *
- * @param String $string String to unescape
- * @return String Unescaped string
- */
-function po_unescape($string) {
-    $replaces = [
-        '\\"' => '"',
-        '\\n' => "\n",
-    ];
-    $string = str_replace(array_keys($replaces), array_values($replaces), $string);
-    return $string;
-}
-
-/**
- * Prepares a string for use in .po file.
- *
- * @param String $string String to use in .po file
- * @return String Processed string
- */
-function po_stringify($string) {
-    $string = str_replace("\r", '', $string);
-    $chunks = explode("\n", $string);
-
-    if (count($chunks) === 1 && mb_strlen($chunks[0]) < MAX_LINE_LENGTH) {
-        return '"' . po_escape($chunks[0]) . '"';
-    }
-
-    $result = '""' . "\n";
-    foreach ($chunks as $index => $chunk) {
-        $chunk = wordwrap($chunk, MAX_LINE_LENGTH);
-        $parts = explode("\n", $chunk);
-        foreach ($parts as $idx => $line) {
-            $current_last = $idx === count($parts) - 1;
-            $last = ($current_last && $index === count($chunks) - 1);
-
-            $result .= '"' . po_escape($line) . ($last ? '' : ($current_last ? '\\n' : ' ')) . '"'. "\n";
-        }
-    }
-    return rtrim($result, "\n");
-}
-
-/**
- * Returns the id for a help entitiy based on the given index and other
- * credentials. This function also copies existing data and settings if
- * the entity in the given language is newly created.
- *
- * @param String $version  Stud.IP version to use for the new entry
- * @param String $language Language to use for the new entry
- * @param Array  $message  Complete message item from parsed .po file
- * @param String $route    Associated route (if any)
- * @param int    $index    Type index for the entity
- * @param int    $position Position/version of the entity
- * @return String Id of the entity
- */
-function get_id($version, $language, $message, $route, $index, $position) {
-    static $ids = [];
-
-    if ($index < 3) {
-        // Entity is help content
-        $hash = md5('content#' . join('#', compact(words('temp version language route position'))));
-    } elseif ($index < 7) {
-        // Entity is help tour content
-        $hash = md5('tour#' . $message['context'] . '#' . join('#' , compact(words('version language'))));
-    } elseif ($index == 7) {
-        // Entity is help tooltip
-        $hash = md5('tooltip#' . $message['context'] . '#' . join(words('position language')));
-    } else {
-        throw new RuntimeException('Unknown index "' . $index . '"');
-    }
-
-    // If id has not yet been generated
-    if (!isset($ids[$hash])) {
-        if ($index < 3) {
-            // Help content
-
-            // Try to get content id by primary key
-            $query = "SELECT content_id
-                      FROM help_content
-                      WHERE route = :route AND studip_version = :version
-                        AND language = :language AND position = :position
-                        AND custom = 0";
-            $statement = DBManager::get()->prepare($query);
-            $statement->bindValue(':route', $route);
-            $statement->bindValue(':version', $version);
-            $statement->bindValue(':language', $language);
-            $statement->bindValue(':position', $position);
-            $statement->execute();
-
-            // Use found id or generate new one
-            $id = $statement->fetchColumn() ?: md5(uniqid('help_content', true));
-            $ids[$hash] = $id;
-        } elseif ($index < 7) {
-            // Help tour
-
-            // Is there any previous generated content?
-            // We have to use the hash generated above as the new id since
-            // there is no other way to exactly identify an already created
-            // entity for the given language and version
-            $query = "SELECT tour_id
-                      FROM help_tours
-                      WHERE tour_id = :tour_id";
-            $statement = DBManager::get()->prepare($query);
-            $statement->bindValue(':tour_id', $hash);
-            $statement->execute();
-
-            $id = $statement->fetchColumn();
-            if (!$id) {
-                // If no previous generated content is available, prepare
-                // database for new content
-                $id = $hash;
-
-                // Copy settings from tour
-                $query = "INSERT INTO help_tours
-                          SELECT :id AS tour_id, '' AS name, '' AS description,
-                                 type, roles, version, :language AS language,
-                                 :version AS studip_version, installation_id,
-                                 UNIX_TIMESTAMP() AS mkdate
-                          FROM help_tours
-                          WHERE tour_id = :tour_id";
-                $statement = DBManager::get()->prepare($query);
-                $statement->bindValue(':id', $id);
-                $statement->bindValue(':language', $language);
-                $statement->bindValue(':version', $version);
-                $statement->bindValue(':tour_id', $message['context']);
-                $statement->execute();
-
-                // Copy individual steps
-                $query = "INSERT INTO help_tour_steps
-                          SELECT :id AS tour_id, step, '' AS title, '' AS tip,
-                                 orientation, interactive, css_selector, route,
-                                 author_id, UNIX_TIMESTAMP() AS mkdate
-                          FROM help_tour_steps
-                          WHERE tour_id = :tour_id";
-                $statement = DBManager::get()->prepare($query);
-                $statement->bindValue(':id', $id);
-                $statement->bindValue(':tour_id', $message['context']);
-                $statement->execute();
-
-                // Copy tour audiences
-                $query = "INSERT INTO help_tour_audiences
-                          SELECT :id AS tour_id, range_id, type
-                          FROM help_tour_audiences
-                          WHERE tour_id = :tour_id";
-                $statement = DBManager::get()->prepare($query);
-                $statement->bindValue(':id', $id);
-                $statement->bindValue(':tour_id', $message['context']);
-                $statement->execute();
-
-                // Copy tour settings
-                $query = "INSERT INTO help_tour_settings
-                          SELECT :id AS tour_id, active, access
-                          FROM help_tour_settings
-                          WHERE tour_id = :tour_id";
-                $statement = DBManager::get()->prepare($query);
-                $statement->bindValue(':id', $id);
-                $statement->bindValue(':tour_id', $message['context']);
-                $statement->execute();
-            }
-            $ids[$hash] = $id;
-        } elseif ($index == 7) {
-            // Help tooltip
-
-            // Nothing needs to be done, just copy the tooltip id
-            // (This is the only table that has the id and version/language
-            // info as primary key)
-            $ids[$hash] = $message['context'];
-        }
-    }
-
-    // Return id from cache
-    return $ids[$hash];
-}
-
-// Error message: Not via cli or invalid parameters
-if (!isset($_SERVER['argv'], $_SERVER['argc']) || $_SERVER['argc'] < 2) {
-    print 'Usage: ' . (@$_SERVER['argv'][0] ?: basename(__FILE__)) . ' [--version] [--language] [--force] <import|export> [file]' . "\n";
-    die(1);
-}
-
-// Parse command line options
-$opts = [
-    'short' => 'v:l:f',
-    'long'  => [
-        'force',
-        'version:',
-        'language:'
-    ]
-];
-$options  = getopt($opts['short'], $opts['long']);
-$force    = isset($options['f']) || isset($options['force']);
-$version  = @$options['version'] ?: @$options['v']
-          ?: DBManager::get()->query("SELECT MAX(studip_version) FROM help_content LIMIT 1")->fetchColumn()
-          ?: $GLOBALS['SOFTWARE_VERSION'];
-$language = @$options['language'] ?: @$options['l'] ?: mb_substr(Config::get()->DEFAULT_LANGUAGE, 0, 2);
-
-// Remove option from arguments
-$remove = [];
-foreach (str_split($opts['short']) as $opt) {
-    if ($opt !== ':') {
-        $remove[] = '-' . $opt;
-    }
-}
-foreach ($opts['long'] as $opt) {
-    $remove[] = '--' . rtrim($opt, ':');
-}
-$_SERVER['argv'] = array_values(array_diff($_SERVER['argv'], $remove));
-
-if ($_SERVER['argv'][1] === 'export') {
-    // Export
-
-    // Get output file name
-    // Either from second parameter or use default at temp path
-    $output   = $_SERVER['argv'][2] ?: ($GLOBALS['TMP_PATH'] . '/studip-help-content-' . $version . '-' . $language . '.po');
-
-    // Error message: Script will not overwrite existing file unless forced
-    if (file_exists($output) && !$force) {
-        printf('Error: Output file "%s" exists. Use --force to overwrite.' . "\n", $output);
-        die(2);
-    }
-
-    // Error message: Output directory does not exist
-    $output_dir = dirname($output);
-    if (!file_exists($output_dir)) {
-        printf('Error: Directory for output "%s" does not exist.' . "\n", $output_dir);
-        die(3);
-    }
-    // Error message: Output directory is not writable
-    if (!is_writable($output_dir)) {
-        printf('Error: Directory for output "%s" is not writable.' . "\n", $output_dir);
-        die(4);
-    }
-
-    // Open output file for writing
-    $fp = fopen($output, 'w+');
-    // Error message: Output file could not be openend for writing
-    if (!is_resource($fp)) {
-        printf('Error: Could not open output file "%s" for writing.' . "\n", $output);
-        die(5);
-    }
-
-    // Write .po header
-    fputs($fp, '# Jan-Hendrik Willms <tleilax+studip@gmail.com>, 2014.' . "\n");
-    fputs($fp, '# Generated content' . "\n");
-    fputs($fp, 'msgid ""' . "\n");
-    fputs($fp, 'msgstr ""' . "\n");
-    fputs($fp, '"Project-Id-Version: STUDIP-' . $GLOBALS['SOFTWARE_VERSION'] . '\\n"' . "\n");
-    fputs($fp, '"Language: STUDIP-' . $language . '\\n"' . "\n");
-    fputs($fp, '"Report-Msgid-Bugs-To: tleilax+studip@gmail.com' . '\\n"' . "\n");
-    fputs($fp, '"POT-Creation-Date: ' . date('r') . '\\n"' . "\n");
-    fputs($fp, '"PO-Revision-Date: ' . date('r') . '\\n"' . "\n");
-    fputs($fp, '"Last-Translator: Stud.IP Core Group <info@studip.de>\\n"' . "\n");
-    fputs($fp, '"Language-Team: Stud.IP Core Group <info@studip.de>\\n"' . "\n");
-    fputs($fp, '"MIME-Version: 1.0\\n"' . "\n");
-    fputs($fp, '"Content-Type: text/plain; charset=UTF-8\\n"' . "\n");
-    fputs($fp, '"Content-Transfer-Encoding: 8bit\\n"' . "\n");
-    fputs($fp, "\n");
-
-    // Load all data from db in one big query
-    $query = "SELECT label AS content, CONCAT(route, ':', 10000 + position) AS occurence
-              FROM help_content
-              WHERE studip_version = :version
-                AND language = :language
-                AND custom = 0
-              -- Help content label
-
-              UNION
-
-              SELECT CONCAT(content, '{#$#}', content_id) AS content, CONCAT(route, ':', 20000 + position) AS occurence
-              FROM help_content
-              WHERE studip_version = :version
-                AND language = :language
-                AND custom = 0
-              -- Actual help content
-
-              UNION
-
-              SELECT CONCAT(name, '{#$#}', tour_id) AS content, CONCAT('tours.php:', 30000 + version) AS occurence
-              FROM help_tours
-              WHERE studip_version = :version
-                AND language = :language
-              -- Help tour name
-
-              UNION
-
-              SELECT CONCAT(description, '{#$#}', tour_id) AS content, CONCAT('tours.php:', 40000 + version) AS occurence
-              FROM help_tours
-              WHERE studip_version = :version
-                AND language = :language
-              -- Help tour description
-
-              UNION
-
-              SELECT CONCAT(title, '{#$#}', tour_id) AS content, CONCAT(route, ':', 50000 + step) AS occurence
-              FROM help_tour_steps
-              JOIN help_tours USING (tour_id)
-              WHERE studip_version = :version
-                AND language = :language
-              -- Individual help tour step title
-
-              UNION
-
-              SELECT CONCAT(tip, '{#$#}', tour_id) AS content, CONCAT(route, ':', 60000 + step) AS occurence
-              FROM help_tour_steps
-              JOIN help_tours USING (tour_id)
-              WHERE studip_version = :version
-                AND language = :language
-              -- Individual help tour step content
-
-              UNION
-
-              SELECT CONCAT(t0.content, '{#$#}', t0.tooltip_id) AS content, CONCAT(t0.route, ':', 70000 + t0.version) AS occurence
-              FROM help_tooltips AS t0
-              LEFT JOIN help_tooltips AS t1
-                     ON     t0.language = t1.language
-                        AND t0.tooltip_id = t1.tooltip_id
-                        AND t0.version < t1.version
-              WHERE t0.language = :language AND t1.tooltip_id IS NULL
-              -- Help tooltip
-              ";
-    $statement = DBManager::get()->prepare($query);
-    $statement->bindValue(':version', $version);
-    $statement->bindValue(':language', $language);
-    $statement->execute();
-    $statement->setFetchMode(PDO::FETCH_GROUP | PDO::FETCH_COLUMN);
-
-    // Loop through each row and write .po entry
-    foreach ($statement as $content => $occurences) {
-        list($content, $context) = explode('{#$#}', $content);
-
-        fputs($fp, '#: ' . implode(' ', $occurences) . "\n");
-        if ($context) {
-            fputs($fp, 'msgctxt "' . $context . '"' . "\n");
-        }
-        fputs($fp, 'msgid ' . po_stringify($content) . "\n");
-        fputs($fp, 'msgstr ""' . "\n");
-        fputs($fp, "\n");
-    }
-
-    // Close output file
-    fclose($fp);
-} elseif ($_SERVER['argv'][1] === 'import') {
-    // Import
-
-    // Error message: Invalid parameters
-    if ($_SERVER['argc'] < 4) {
-        print 'Usage: ' . $_SERVER['argv'][0] . ' import [--language] <file> <version>';
-        die(6);
-    }
-
-    // Set input file and version from parameters
-    $input   = $_SERVER['argv'][2];
-    $version = $_SERVER['argv'][3];
-
-    // Error message: Input file does not exists or is not readable
-    if (!file_exists($input) || !is_readable($input)) {
-        printf('Error: Input file "%s" does not exist or is not readable.' . "\n", $input);
-        die(7);
-    }
-
-    // Open input file for reading
-    $fp = fopen($input, 'r');
-    // Error message: Input file could not be opened for reading
-    if (!is_resource($fp)) {
-        printf('Error: Could not open input file "%s" for reading.' . "\n", $input);
-        die(5);
-    }
-
-    // Parse input .po file
-    // This is pretty straight forward, yet hacky.
-    // The script tries to detect comments (only #:, # by itself is ignored),
-    // message context, message id and message content in this order.
-    // Any empty line will write to messages array.
-    // This routine will probably break for any .po file that differs from the
-    // ones created in transifex.
-    // This is just supposed to work, not to be beautiful. ;)
-    $messages = [];
-    $context    = '';
-    $id         = '';
-    $content    = '';
-    $occurences = [];
-    $last       = false;
-    $count      = 0;
-    while (!feof($fp) && $row = fgets($fp)) {
-        $count += 1;
-
-        $row = trim($row);
-        if ($row[0] === '#' && $row[1] !== ':') {
-            continue;
-        }
-        if ($row[0] === '#') {
-            $occurences = array_merge($occurences, explode(' ', mb_substr($row, 2)));
-            $occurences = array_filter($occurences);
-            $last = 'occurence';
-        } elseif (preg_match('/^\msgctxt\\s+"(.*?)"$/', $row, $match)) {
-            $context = $match[1];
-            $last = 'context';
-        } elseif (preg_match('/^msgid\\s+"(.*?)"$/', $row, $match)) {
-            $id = po_unescape($match[1]);
-            $last = 'id';
-        } elseif (preg_match('/^msgstr\\s+"(.*?)"$/', $row, $match)) {
-            $content = po_unescape($match[1]);
-            $last = 'content';
-        } elseif (preg_match('/^"(.*?)"$/', $row, $match) && in_array($last, words('id content'))) {
-            if ($last === 'id') {
-                $id .= po_unescape($match[1]);
-            } else {
-                $content .= po_unescape($match[1]);
-            }
-        } elseif (!$row && $last === 'content') {
-            $messages[$context . '#' . $id] = compact(words('context id content occurences'));
-
-            $context    = '';
-            $id         = '';
-            $content    = '';
-            $occurences = [];
-            $last       = false;
-        } else {
-            printf('Parse error at line %u.' . "\n", $count);
-            printf('Last item was "%s".' . "\n", $last);
-            printf('Current row: %s' . "\n", $row);
-            die(6);
-        }
-    }
-    fclose($fp);
-
-    // Parse meta information (no context & no id = item at '#')
-    $meta = [];
-    foreach (explode("\n", $messages['#']['content']) as $row) {
-        $row = trim($row);
-        if (!$row) {
-            continue;
-        }
-
-        list($index, $content) = array_map('trim', explode(':', $row, 2));
-        $meta[$index] = $content;
-    }
-    unset($messages['#']);
-
-    // Get language
-    $language = mb_strtolower($meta['Language']);
-
-    // Define db queries for each type (see comment block at the top of
-    // this file, type is distinguished by the line number / 10000)
-    $queries = [];
-    $queries[1] = "INSERT INTO help_content (content_id, language, label, icon, content, route, studip_version, position, custom, installation_id, mkdate)
-                   VALUES (:id, :language, :content, 'info', '', :route, :version, :position, 0, '', UNIX_TIMESTAMP())
-                   ON DUPLICATE KEY UPDATE label = VALUES(label)";
-    $queries[2] = "INSERT INTO help_content (content_id, language, label, icon, content, route, studip_version, position, custom, installation_id, mkdate)
-                   VALUES (:id, :language, '', 'info', :content, :route, :version, :position, 0, '', UNIX_TIMESTAMP())
-                   ON DUPLICATE KEY UPDATE content = VALUES(content)";
-    $queries[3] = "INSERT INTO help_tours (tour_id, name, description, type, roles, version, language, studip_version, installation_id, mkdate)
-                   VALUES (:id, :content, '', 'tour', '', :position, :language, :version, '', UNIX_TIMESTAMP())
-                   ON DUPLICATE KEY UPDATE name = VALUES(name)";
-    $queries[4] = "INSERT INTO help_tours (tour_id, name, description, type, roles, version, language, studip_version, installation_id, mkdate)
-                   VALUES (:id, '', :content, 'tour', '', :position, :language, :version, '', UNIX_TIMESTAMP())
-                   ON DUPLICATE KEY UPDATE description = VALUES(description)";
-    $queries[5] = "INSERT INTO help_tour_steps (tour_id, step, title, tip, interactive, css_selector, route, author_id, mkdate)
-                   VALUES (:id, :position, :content, '', 0, '', :route, '', UNIX_TIMESTAMP())
-                   ON DUPLICATE KEY UPDATE title = VALUES(title)";
-    $queries[6] = "INSERT INTO help_tour_steps (tour_id, step, title, tip, interactive, css_selector, route, author_id, mkdate)
-                   VALUES (:id, :position, '', :content, 0, '', :route, '', UNIX_TIMESTAMP())
-                   ON DUPLICATE KEY UPDATE tip = VALUES(tip)";
-    $queries[7] = "INSERT INTO help_tooltips (tooltip_id, language, version, content, author_id, mkdate, route)
-                   VALUES (:id, :language, :position, :content, '', UNIX_TIMESTAMP(), :route)
-                   ON DUPLICATE KEY UPDATE content = VALUES(content)";
-
-    // Prepare statements and prebind version and language
-    $statements = array_map([DBManager::get(), 'prepare'], $queries);
-    foreach ($statements as $index => $statement) {
-        $statement->bindValue(':version', $version);
-        $statement->bindValue(':language', $language);
-
-        $statements[$index] = $statement;
-    }
-
-    // Process each message, skip the ones with empty content
-    foreach ($messages as $message) {
-        if (empty($message['content'])) {
-            continue;
-        }
-
-        foreach ($message['occurences'] as $occurence) {
-            list($route, $lineno) = explode(':', $occurence);
-            $index    = floor($lineno / 10000);
-            $position = $lineno % 10000;
-
-            $id = get_id($version, $language, $message, $route, $index, $position);
-
-            $statement = $statements[$index];
-            $statement->bindValue(':id', $id);
-            $statement->bindValue(':content', $message['content']);
-            $statement->bindValue(':route', $route);
-            $statement->bindValue(':position', $position);
-            $statement->execute();
-        }
-    }
-}
diff --git a/cli/i18n-plugin.php b/cli/i18n-plugin.php
deleted file mode 100755
index e3014131f921c2c6f88863fe3f1593ff6237e106..0000000000000000000000000000000000000000
--- a/cli/i18n-plugin.php
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once 'studip_cli_env.inc.php';
-
-if ($_SERVER['argc'] < 3) {
-    fwrite(STDOUT, 'Stud.IP plugin localization tool - Tools for the localization of a plugin' . PHP_EOL);
-    fwrite(STDOUT, '=========================================================================' . PHP_EOL);
-    fwrite(STDOUT, 'Usage: ' . basename(__FILE__) . ' <folder> <command>' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    fwrite(STDOUT, '<folder> is the folder of the plugin you want to localize.' . PHP_EOL);
-    fwrite(STDOUT, '<command> is any of the commands listed below.' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    fwrite(STDOUT, 'Commands:' . PHP_EOL);
-    fwrite(STDOUT, '  detect  - Detects probably unmarked strings for localization in php files.' . PHP_EOL);
-    fwrite(STDOUT, '  extract - Extracts the localizable string from php files into a .pot file.' . PHP_EOL);
-    fwrite(STDOUT, '  compile - Compiles all .po files in the locale folder of the plugin' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    exit(0);
-}
-
-$plugin_folder = $_SERVER['argv'][1];
-$command       = $_SERVER['argv'][2];
-
-if (!is_dir($plugin_folder)) {
-    $plugin_folder = rtrim($GLOBALS['ABSOLUTE_PATH_STUDIP'], '/') . '/' . ltrim($plugin_folder, '/');
-}
-if (!is_dir($plugin_folder)) {
-    fwrite(STDERR, 'Error: ' . $_SERVER['argv'][2] . ' is not a valid folder' . PHP_EOL);
-    exit(0);
-}
-
-$plugin_folder = rtrim($plugin_folder, '/');
-
-if (!file_exists($plugin_folder. '/plugin.manifest')) {
-    fwrite(STDERR, 'Error: ' . $_SERVER['argv'][2] . ' is not a valid plugin folder. Manifest is missing.' . PHP_EOL);
-    exit(0);
-}
-$manifest = parse_ini_file($plugin_folder . '/plugin.manifest', false,  INI_SCANNER_RAW);
-
-$languages = array_map(function ($lang) {
-    return explode('_', $lang)[0];
-}, array_keys($GLOBALS['INSTALLED_LANGUAGES']));
-
-if ($command === 'detect') {
-    $iterator = new RecursiveDirectoryIterator($plugin_folder, FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS);
-    $iterator = new RecursiveIteratorIterator($iterator);
-    $regexp_iterator = new RegexIterator($iterator, '/\.php$/', RecursiveRegexIterator::MATCH);
-
-    foreach ($regexp_iterator as $file) {
-        $filename = $file->getPathName();
-        if (preg_match('/(?<![$>])_\(/', file_get_contents($filename))) {
-            fwrite(STDOUT, "{$filename}" . PHP_EOL);
-        }
-    }
-
-    // system("ack -l '(?<![$>])_\(' {$plugin_folder}");
-} elseif ($command === 'extract') {
-    if (!isset($manifest['localedomain'])) {
-        fwrite(STD_ERROR, 'No localedomain found in plugin manifest' . PHP_EOL);
-    }
-
-    $pot_name = $manifest['localedomain'];
-
-    foreach (array_keys($GLOBALS['CONTENT_LANGUAGES']) as $lang) {
-        $lang = explode('_', $lang)[0];
-        $language_dir = "{$plugin_folder}/locale/{$lang}/LC_MESSAGES";
-        if (!file_exists($language_dir)) {
-            mkdir($language_dir, 0755, true);
-        }
-    }
-
-    $main_lang = reset($languages);
-    $pot_file  = "{$plugin_folder}/locale/{$main_lang}/LC_MESSAGES/{$pot_name}.pot";
-    file_put_contents($pot_file, '');
-
-    system("find {$plugin_folder} -iname '*.php' | xargs xgettext --keyword=_n:1,2 --from-code=UTF-8 -j -n --language=PHP --add-location=never --package-name={$manifest['pluginclassname']} -o {$pot_file}");
-} elseif ($command === 'compile') {
-    foreach (glob("{$plugin_folder}/locale/*/LC_MESSAGES/*.po") as $po) {
-        $mo = preg_replace('/\.po$/', '.mo', $po);
-        system("msgfmt {$po} -o {$mo}");
-    }
-
-} else {
-    fwrite(STDERR, 'Unknown command: ' . $_SERVER['argv'][1] . PHP_EOL);
-    exit(0);
-}
-
-exit(1);
-
diff --git a/cli/kill_studip_user.php b/cli/kill_studip_user.php
deleted file mode 100755
index 7798fa44a40c4dc97b06ac1bff078b70e1a7b7ca..0000000000000000000000000000000000000000
--- a/cli/kill_studip_user.php
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env php
-<?php
-# Lifter003: TEST
-# Lifter007: TODO
-/**
-* kill_studip_user.php
-*
-*
-*
-*
-* @author       André Noack <noack@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de>
-* @access       public
-*/
-// +---------------------------------------------------------------------------+
-// This file is part of Stud.IP
-// kill_studip_user.php
-//
-// Copyright (C) 2006 André Noack <noack@data-quest.de>,
-// Suchi & Berg GmbH <info@data-quest.de>
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-define('SEND_MAIL_ON_DELETE', 1);
-define('KILL_ADMINS' , 0);
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-
-if (SEND_MAIL_ON_DELETE && !($MAIL_LOCALHOST && $MAIL_HOST_NAME && $ABSOLUTE_URI_STUDIP)){
-    trigger_error('To use this script you MUST set correct values for $MAIL_LOCALHOST, $MAIL_HOST_NAME and $ABSOLUTE_URI_STUDIP in local.inc!', E_USER_ERROR);
-}
-
-$argc = $_SERVER['argc'];
-$argv = $_SERVER['argv'];
-
-if (!$argv[1]){
-    fwrite(STDOUT,'Usage: ' . basename(__FILE__) . ' [file][-] (use - to read from STDIN)' .chr(10));
-    exit(0);
-}
-if ($argv[1] == '-'){
-    $fo = STDIN;
-} elseif (is_file($argv[1])){
-    $fo = fopen($argv[1],'r');
-} else {
-    trigger_error("File not found: {$argv[1]}", E_USER_ERROR);
-}
-
-$list = '';
-while (!feof($fo)) {
-  $list .= fgets($fo, 1024);
-}
-
-$kill_list = preg_split("/[\s,;]+/", $list, -1, PREG_SPLIT_NO_EMPTY);
-$kill_list = array_unique($kill_list);
-
-$query = "SELECT * FROM auth_user_md5 WHERE username IN (?)";
-$statement = DBManager::get()->prepare($query);
-$statement->execute([$kill_list ?: '']);
-while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-    $kill_user[$row['username']] = $row;
-}
-if (!is_array($kill_user)) {
-    fwrite(STDOUT, 'No user from list found in database.' . chr(10));
-    exit(0);
-}
-
-foreach($kill_user as $uname => $udetail){
-    if (!KILL_ADMINS && ($udetail['perms'] == 'admin' || $udetail['perms'] == 'root')){
-        fwrite(STDOUT, "user: $uname is '{$udetail['perms']}', NOT deleted". chr(10));
-    } else {
-        $umanager = new UserManagement($udetail['user_id']);
-        //wenn keine Email gewünscht, Adresse aus den Daten löschen
-        if (!SEND_MAIL_ON_DELETE) $umanager->user_data['auth_user_md5.Email'] = '';
-        if ($umanager->deleteUser()){
-            fwrite(STDOUT, "user: $uname successfully deleted:". chr(10)
-            . parse_msg_to_clean_text($umanager->msg)
-            . chr(10));
-        } else {
-            fwrite(STDOUT, "user: $uname NOT deleted:". chr(10)
-            . parse_msg_to_clean_text($umanager->msg)
-            . chr(10));
-        }
-    }
-}
-exit(1);
diff --git a/cli/migrate.php b/cli/migrate.php
deleted file mode 100755
index 618132a8b8255fc0ab8f7dbf51e8f9b835d0daf5..0000000000000000000000000000000000000000
--- a/cli/migrate.php
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env php
-<?php
-# Lifter007: TODO
-# Lifter003: TODO
-/*
- * migrate.php - Migrations for Stud.IP
- *
- * Copyright (C) 2006 - Marcus Lunzenauer <mlunzena@uos.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-
-if (isset($_SERVER['argv'])) {
-    # check for command line options
-    $options = getopt('b:d:lm:t:v');
-    if ($options === false) {
-        exit(1);
-    }
-
-    # check for options
-    $domain = 'studip';
-    $branch = '0';
-    $list = false;
-    $path = $STUDIP_BASE_PATH . '/db/migrations';
-    $verbose = false;
-    $target = null;
-
-    foreach ($options as $option => $value) {
-        switch ($option) {
-            case 'b':
-                $branch = (string) $value;
-                break;
-            case 'd':
-                $domain = (string) $value;
-                break;
-            case 'l':
-                $list = true;
-                break;
-            case 'm':
-                $path = $value;
-                break;
-            case 't':
-                $target = (int) $value;
-                break;
-            case 'v':
-                $verbose = true;
-                break;
-        }
-    }
-
-    $version = new DBSchemaVersion($domain, $branch);
-    $migrator = new Migrator($path, $version, $verbose);
-
-    if ($list) {
-        $migrations = $migrator->relevantMigrations($target);
-
-        foreach ($migrations as $number => $migration) {
-            $description = $migration->description() ?: '(no description)';
-            printf("%6s %-20s %s\n", $number, get_class($migration), $description);
-        }
-    } else {
-        $migrator->migrateTo($target);
-    }
-}
diff --git a/cli/migrate_help_content.php b/cli/migrate_help_content.php
deleted file mode 100755
index 71b4c663174e855ac6fe6696b09c25f488de8ce4..0000000000000000000000000000000000000000
--- a/cli/migrate_help_content.php
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
-* migrate_help_content.php
-*
-* @author       Arne Schröder <schroeder@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de>
-* @access       public
-*/
-// +---------------------------------------------------------------------------+
-// This file is part of Stud.IP
-//
-// Copyright (C) 2014 Arne Schröder <schroeder@data-quest.de>,
-// Suchi & Berg GmbH <info@data-quest.de>
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-
-require_once __DIR__ . '/studip_cli_env.inc.php';
-
-$help_path = __DIR__ . '/../doc/helpbar';
-
-$argc = $_SERVER['argc'];
-$argv = $_SERVER['argv'];
-
-if (!$argv[2]){
-    fwrite(STDOUT,'Usage: ' . basename(__FILE__) . ' [version] [language]' .chr(10));
-    exit(0);
-}
-
-$query = "SELECT * FROM help_content WHERE studip_version = ? LIMIT 1";
-$statement = DBManager::get()->prepare($query);
-$statement->execute([$argv[1]]);
-$ret = $statement->fetchGrouped(PDO::FETCH_ASSOC);
-if (count($ret)) {
-    trigger_error('Helpbar content already present for this version!', E_USER_ERROR);
-}
-
-$filename = $help_path .'/'. $argv[2] . '/helpcontent.json';
-if (is_file($filename)){
-    $json = json_decode(file_get_contents($filename), true);
-} else {
-    trigger_error("File not found: ".$filename, E_USER_ERROR);
-}
-
-if ($json === null) {
-    trigger_error('Helpbar content could not be loaded. File: '.$filename, E_USER_ERROR);
-}
-
-foreach ($json as $row) {
-    if (!is_array($row['text']))
-        $row['text'] = [$row['text']];
-        if (!$row['label'])
-            $row['label'] = '';
-        if (!$row['icon'])
-            $row['icon'] = '';
-            foreach ($row['text'] as $index => $text) {
-        $count[$argv[2].$row['route']]++;
-        $query = "INSERT INTO help_content (content_id, language, label, icon, content, route, studip_version, position, custom, visible, author_id, installation_id, mkdate)
-                  VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 1, '', ?, UNIX_TIMESTAMP())";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([md5(uniqid(rand(), true)), $argv[2], ($index == 0 ? $row['label'] : ''), ($index == 0 ? $row['icon'] : ''), $text, $row['route'], $argv[1], $count[$argv[2].$row['route']], Config::get()->STUDIP_INSTALLATION_ID]);
-    }
-}
-if (count($count)) {
-    if (!Config::get()->getValue('HELP_CONTENT_CURRENT_VERSION'))
-        Config::get()->create('HELP_CONTENT_CURRENT_VERSION', [
-            'value' => $argv[1],
-            'is_default' => 0,
-            'type' => 'string',
-            'range' => 'global',
-            'section' => 'global',
-            'description' => _('Aktuelle Version der Helpbar-Einträge in Stud.IP')
-            ]);
-    else
-        Config::get()->store('HELP_CONTENT_CURRENT_VERSION', $argv[1]);
-}
-fwrite(STDOUT, 'help content added for '.count($count).' routes.' . chr(10));
-exit(1);
diff --git a/cli/myisam_to_innodb.php b/cli/myisam_to_innodb.php
deleted file mode 100755
index 85dab426a31248113a92e178eba2a31b8c1da470..0000000000000000000000000000000000000000
--- a/cli/myisam_to_innodb.php
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once(__DIR__.'/studip_cli_env.inc.php');
-
-echo 'Migration starting at '.date('d.m.Y H:i:s').".\n";
-$start = microtime(true);
-
-global $DB_STUDIP_DATABASE;
-
-// Check if InnoDB is enabled in database server.
-$engines = DBManager::get()->fetchAll("SHOW ENGINES");
-$innodb = false;
-foreach ($engines as $e) {
-    // InnoDB is found and enabled.
-            if ($e['Engine'] == 'InnoDB' && in_array(mb_strtolower($e['Support']), ['default', 'yes'])) {
-        $innodb = true;
-        break;
-    }
-}
-
-if ($innodb) {
-    // Get version of database system (MySQL/MariaDB/Percona)
-    $data = DBManager::get()->fetchFirst("SELECT VERSION() AS version");
-    $version = $data[0];
-
-    // Tables to ignore on engine conversion.
-    $ignore_tables = [];
-
-
-
-
-    // Fetch all tables that need to be converted.
-    $tables = DBManager::get()->fetchFirst("SELECT TABLE_NAME
-        FROM `information_schema`.TABLES
-        WHERE TABLE_SCHEMA=:database AND ENGINE=:oldengine
-        ORDER BY TABLE_NAME",
-        [
-            ':database' => $DB_STUDIP_DATABASE,
-            ':oldengine' => 'MyISAM',
-        ]);
-
-            /*
-             * lit_catalog needs fulltext indices which InnoDB doesn't support
-             * in older versions.
-             */
-            if (version_compare($version, '5.6', '<')) {
-                $stmt_fulltext = DBManager::get()->prepare("SHOW INDEX FROM :database.:table WHERE Index_type = 'FULLTEXT'");
-                foreach ($tables as $k => $t) {
-                    $stmt_fulltext->bindParam(':table', $t, StudipPDO::PARAM_COLUMN);
-                    $stmt_fulltext->bindParam(':database', $DB_STUDIP_DATABASE, StudipPDO::PARAM_COLUMN);
-                    $stmt_fulltext->execute();
-                    if ($stmt_fulltext->fetch()) {
-                        $ignore_tables[] = $t;
-                        unset($tables[$k]);
-                    }
-                }
-                if (count($ignore_tables)) {
-                    echo 'The following tables needs fulltext indices '.
-                        'which are not supported for InnoDB in your database '.
-                        'version, so the tables will be left untouched: ' . join(',', $ignore_tables) . "\n";
-                }
-            }
-
-
-    // Use Barracuda format if database supports it (5.5 upwards).
-    if (version_compare($version, '5.5', '>=')) {
-        echo "\tFound MySQL in version >= 5.5, checking if Barracuda file format is supported...";
-        // Get innodb_file_per_table setting
-        $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_per_table'");
-        $file_per_table = $data['Value'];
-
-        // Check if Barracuda file format is enabled
-        $data = DBManager::get()->fetchOne("SHOW VARIABLES LIKE 'innodb_file_format'");
-        $file_format = $data['Value'];
-
-        if (mb_strtolower($file_per_table) == 'on' && mb_strtolower($file_format) == 'barracuda') {
-            echo " yes.\n";
-            $rowformat = 'DYNAMIC';
-        } else {
-            echo " no:\n";
-            if (mb_strtolower($file_per_table) != 'on') {
-                echo "\t- file_per_table not set\n";
-            }
-            if (mb_strtolower($file_format) != 'barracuda') {
-                echo "\t- file_format not set to Barracuda (but to " . $file_format . ")\n";
-            }
-            $rowformat = 'COMPACT';
-        }
-    }
-
-    // Prepare query for table conversion.
-    $stmt = DBManager::get()->prepare("ALTER TABLE :database.:table ROW_FORMAT=:rowformat ENGINE=:newengine");
-    $stmt->bindParam(':database', $DB_STUDIP_DATABASE, StudipPDO::PARAM_COLUMN);
-    $stmt->bindParam(':rowformat', $rowformat, StudipPDO::PARAM_COLUMN);
-    $newengine = 'InnoDB';
-    $stmt->bindParam(':newengine', $newengine, StudipPDO::PARAM_COLUMN);
-
-    // Now convert the found tables.
-    foreach ($tables as $t) {
-        $local_start = microtime(true);
-        $stmt->bindParam(':table', $t, StudipPDO::PARAM_COLUMN);
-        $stmt->execute();
-        $local_end = microtime(true);
-        $local_duration = $local_end - $local_start;
-        $human_local_duration = sprintf("%02d:%02d:%02d",
-            ($local_duration / 60 / 60) % 24, ($local_duration / 60) % 60, $local_duration % 60);
-
-        echo "\tConversion of table " . $t . " took " . $human_local_duration . ".\n";
-    }
-
-
-    $end = microtime(true);
-
-    $duration = $end - $start;
-    $human_duration = sprintf("%02d:%02d:%02d",
-        ($duration / 60 / 60) % 24, ($duration / 60) % 60, $duration % 60);
-
-    echo 'Migration finished at ' . date('d.m.Y H:i:s') . ', duration ' . $human_duration . ".\n";
-} else {
-    echo "The storage engine InnoDB is not enabled in your ".
-        "database installation, tables cannot be converted.\n";
-}
diff --git a/cli/plugin_manager b/cli/plugin_manager
deleted file mode 100755
index e566c75a13ef419f74a5b375debda62637c29d2c..0000000000000000000000000000000000000000
--- a/cli/plugin_manager
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env php
-<?php
-/*
- * plugin_manager.php - CLI Plugin-Manager for Stud.IP
- *
- * Detailed documentation of this cli-script can be found at:
- * http://docs.studip.de/develop/Entwickler/CLIPluginManager
- *
- * Copyright (C) 2012 - Till Glöggler <tgloeggl@uos.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
- */
-
-require_once 'studip_cli_env.inc.php';
-require_once 'cli/getopts.php';
-
-$args = $_SERVER['argv'];
-
-if ($args) {
-
-    $command = $args[1];
-
-     if (!$command) {
-        echo 'Usage: '. $args[0] .' {install|register|unregister|migrate|activate|deactivate|info|scan}' . "\n";
-    }
-
-    switch ($command) {
-        case 'install':
-            $zipfile = $args[2];
-
-            // show usage
-            if (!$zipfile) {
-                echo 'Usage: '. $args[0] .' install PATH/TO/PLUGIN.ZIP' . "\n\n";
-                exit(1);
-            }
-
-            $plugin_admin = new PluginAdministration();
-
-            try {
-                if (parse_url($zipfile, PHP_URL_SCHEME)) {
-                    $plugin_admin->installPluginFromURL($zipfile);
-                } else {
-                    $plugin_admin->installPlugin($zipfile);
-                }
-                echo 'Das Plugin wurde erfolgreich installiert.' . "\n";
-            } catch (PluginInstallationException $ex) {
-                echo $ex->getMessage() . "\n";
-            }
-
-            exit(0);
-        break;
-
-        case 'register':
-            $plugindir = $args[2];
-
-            // show usage
-            if (!$plugindir) {
-                echo 'Usage: '. $args[0] .' register PATH/TO/PLUGIN' . "\n\n";
-                # echo 'Options:' . "\n";
-                # echo "\t". '-f force installation and try to (re-)execute any sql-scripts associated' ."\n";
-                exit(1);
-            }
-
-            # $options = getopts(':f'); // if f is set, try to execute the plugins sql-scripts (if any)
-
-            $plugin_manager = PluginManager::getInstance();
-            $manifest = $plugin_manager->getPluginManifest($plugindir);
-
-            if (!$manifest) {
-                echo 'Das Plugin-Manifest fehlt!' . "\n";
-                exit(1);
-            }
-
-            // get plugin meta data
-            $pluginclass = $manifest['pluginclassname'];
-            $origin      = $manifest['origin'];
-            $min_version = $manifest['studipMinVersion'];
-            $max_version = $manifest['studipMaxVersion'];
-
-            // check for compatible version
-            if ((isset($min_version) && StudipVersion::olderThan($min_version)) ||
-                (isset($max_version) && StudipVersion::newerThan($max_version))) {
-                throw new PluginInstallationException(_('Das Plugin ist mit dieser Stud.IP-Version nicht kompatibel.'));
-            }
-
-            // determine the plugin path
-            $basepath = Config::get()->PLUGINS_PATH;
-            $pluginpath = $origin . '/' . $pluginclass;
-
-            $plugin_manager = PluginManager::getInstance();
-            $pluginregistered = $plugin_manager->getPluginInfo($pluginclass);
-
-            // create database schema if needed
-            if (isset($manifest['dbscheme']) && !$pluginregistered) {
-                $schemafile = $plugindir . '/' . $manifest['dbscheme'];
-                $contents   = file_get_contents($schemafile);
-                $statements = preg_split("/;[[:space:]]*\n/", $contents, -1, PREG_SPLIT_NO_EMPTY);
-                $db = DBManager::get();
-                foreach ($statements as $statement) {
-                    $db->exec($statement);
-                }
-            }
-
-            // check for migrations
-            if (is_dir($plugindir . '/migrations')) {
-                $schema_version = new DBSchemaVersion($manifest['pluginname']);
-                $migrator = new Migrator($plugindir . '/migrations', $schema_version);
-                $migrator->migrateTo(null);
-            }
-
-            // now register the plugin in the database
-            $pluginid = $plugin_manager->registerPlugin($manifest['pluginname'], $pluginclass, $pluginpath);
-
-            // register additional plugin classes in this package
-            $additionalclasses = $manifest['additionalclasses'];
-
-            if (is_array($additionalclasses)) {
-                foreach ($additionalclasses as $class) {
-                    $plugin_manager->registerPlugin($class, $class, $pluginpath, $pluginid);
-                }
-            }
-
-            echo 'Das Plugin '. $manifest['pluginname'] .' wurde erfolgreich eingetragen.' . "\n";
-        break;
-
-        case 'migrate':
-            $pluginname  = $args[2];
-            unset($args[0], $args[1], $args[2]);
-
-            // show usage
-            if (!$pluginname) {
-                echo 'Usage: '. $args[0] .' migrate PLUGINNAME [-l] [-v] [-t target] [-b branch]' . "\n";
-                exit(1);
-            }
-
-            // parse options
-            list($errors, $options, $args) = getopts(array('l' => 'Ss l list', 'v' => 'Ss v verbose', 't' => 'Vs t target', 'b' => 'Vs b branch'));
-            $list = false;
-            $verbose = false;
-            $target = NULL;
-            $branch = '0';
-
-            foreach ($options as $option => $value) {
-                switch ($option) {
-                    case 'b':
-                        $branch = ($value === false) ? '0' : $value;
-                        break;
-                    case 'l':
-                        $list = $value;
-                        break;
-                    case 't':
-                        $target = ($value === false) ? null : (int) $value;
-                        break;
-                    case 'v':
-                        $verbose = $value;
-                        break;
-                }
-            }
-
-            // create plugin-manager and search for plugin by name
-            $plugin_manager = PluginManager::getInstance();
-            $plugins = $plugin_manager->getPluginInfos();
-
-            foreach ($plugins as $plugin) {
-                if (mb_strtolower($pluginname) === mb_strtolower($plugin['name'])) {
-                    $plugindir = Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
-
-                    if (is_dir($plugindir . '/migrations')) {
-                        // if there are migrations, migrate
-                        $schema_version = new DBSchemaVersion($plugin['name'], $branch);
-                        $migrator = new Migrator($plugindir . '/migrations', $schema_version, $verbose);
-
-                        if ($list) {
-                            $migrations = $migrator->relevantMigrations($target);
-
-                            foreach ($migrations as $number => $migration) {
-                                $description = $migration->description() ?: '(no description)';
-
-                                printf("%6s %-20s %s\n", $number, get_class($migration), $description);
-                            }
-                        } else {
-                            $migrator->migrateTo($target);
-                        }
-
-                        exit(0);
-                    } else {
-                        echo 'Konnte keine Migrationen für das Plugin '. $plugin['name'] .' finden.' . "\n";
-                        exit(1);
-                    }
-                }
-            }
-
-            echo 'Konnte kein Plugin mit dem Namen ' . $pluginname . ' finden.' . "\n";
-            echo 'Überprüfen sie bitte den Namen (auch auf Groß-/Kleinschreibung!)' ."\n";
-            exit(1);
-        break;
-
-        case 'unregister':
-            $pluginname  = $args[2];
-
-            // show usage
-            if (!$pluginname) {
-                echo 'Usage: '. $args[0] .' unregister PLUGINNAME' . "\n";
-                exit(1);
-            }
-
-            $plugin_manager = PluginManager::getInstance();
-            $plugins = $plugin_manager->getPluginInfos();
-            foreach ($plugins as $plugin) {
-                if (mb_strtolower($pluginname) == mb_strtolower($plugin['name'])) {
-                    $plugindir = Config::get()->PLUGINS_PATH .'/'. $plugin['path'];
-
-                    $plugin_manager->unregisterPlugin($plugin['id']);
-
-                    if (is_dir($plugindir . '/migrations')) {
-                        $schema_version = new DBSchemaVersion($plugin['name']);
-                        $migrator = new Migrator($plugindir . '/migrations', $schema_version);
-                        $migrator->migrateTo(0);
-                    }
-
-                    echo 'Das Plugin '. $plugin['name'] .' wurde ausgetragen.' . "\n";
-                    exit(0);
-                }
-            }
-
-            echo 'Konnte kein Plugin mit dem Namen '. $pluginname .' finden.' . "\n";
-            echo 'Überprüfen sie bitte den Namen (auch auf Groß-/Kleinschreibung!)' ."\n";
-            exit(1);
-        break;
-
-        case 'activate':
-        case 'deactivate':
-            $pluginname  = $args[2];
-
-            // show usage
-            if (!$pluginname) {
-                echo 'Usage: '. $args[0] .' '. $command .' PLUGINNAME' . "\n";
-                exit(1);
-            }
-
-            $plugin_manager = PluginManager::getInstance();
-            $plugins = $plugin_manager->getPluginInfos();
-            foreach ($plugins as $plugin) {
-                if (mb_strtolower($pluginname) == mb_strtolower($plugin['name'])) {
-                    $plugin_manager->setPluginEnabled($plugin['id'], ($command == 'activate'));
-                    echo 'Das Plugin '. $plugin['name'] .' wurde ' . ($command == 'activate' ? 'aktiviert' : 'deaktiviert') . '.' . "\n";
-                    exit(0);
-                }
-            }
-
-            echo 'Konnte kein Plugin mit dem Namen '. $pluginname .' finden.' . "\n";
-            echo 'Überprüfen sie bitte den Namen (auch auf Groß-/Kleinschreibung!)' ."\n";
-            exit(1);
-        break;
-
-        case 'info':
-            $pluginname  = $args[2];
-
-            $plugin_manager = PluginManager::getInstance();
-            $plugins = $plugin_manager->getPluginInfos();
-            if ($pluginname) {
-                $plugins = array_filter($plugins, function($p) use ($pluginname) {return mb_stripos($p['name'], $pluginname) !== false;});
-            }
-            $basepath = Config::get()->PLUGINS_PATH;
-            foreach ($plugins as $plugin) {
-                $plugindir = $basepath . '/' . $plugin['path'] . '/';
-                $plugin['class_exists'] = 0;
-                $pluginfile = $plugindir . $plugin['class'] . '.class.php';
-                if (file_exists($pluginfile)) {
-                    $plugin['class_exists'] = 1;
-                } else {
-                    $pluginfile = $plugindir . $plugin['class'] . '.php';
-                    if (file_exists($pluginfile)) {
-                        $plugin['class_exists'] = 1;
-                    }
-                }
-                if (is_dir($plugindir . '/migrations')) {
-                    $schema_version = new DBSchemaVersion($plugin['name']);
-                    $migrator = new Migrator($plugindir .'/migrations', $schema_version);
-                    $plugin['migration_top_version'] = $migrator->topVersion();
-                    $plugin['schema_version'] = $schema_version->get();
-                }
-                echo "\n";
-                $plugin['type'] = join(',' , $plugin['type']);
-                echo join("\n", array_filter(array_map(function($p){if ($p[0] == ' ') return trim($p);},explode("\n", print_r($plugin,1)))));
-                echo "\n";
-            }
-            exit(0);
-        break;
-
-        case 'scan':
-            $plugin_admin = new PluginAdministration();
-            $plugin_manager = PluginManager::getInstance();
-            foreach ($plugin_admin->scanPluginDirectory() as $manifest) {
-                if (!$plugin_manager->getPluginInfo($manifest['pluginclassname'])) {
-                    echo "\n";
-                    echo join("\n", array_filter(array_map(function($p){if ($p[0] == ' ') return trim($p);},explode("\n", print_r($manifest,1)))));
-                    echo "\n";
-                }
-            }
-            exit(0);
-        break;
-    }
-
-}
-
-exit(0);
diff --git a/cli/studip b/cli/studip
new file mode 100755
index 0000000000000000000000000000000000000000..b723ad58ae62e73d09bb19524a41902920143280
--- /dev/null
+++ b/cli/studip
@@ -0,0 +1,62 @@
+#!/usr/bin/env php
+<?php
+
+namespace Studip\Cli;
+
+use Symfony\Component\Console\Application;
+
+require __DIR__.'/studip_cli_env.inc.php';
+require __DIR__.'/../composer/autoload.php';
+
+\StudipAutoloader::addAutoloadPath('cli', 'Studip\\Cli');
+
+$application = new Application();
+$commands = [
+    Commands\Base\Dump::class,
+    Commands\Checks\Compatibility::class,
+    Commands\Checks\GlobalizedConfig::class,
+    Commands\Checks\HelpTours::class,
+    Commands\Checks\HelpTours::class,
+    Commands\CleanupAdmissionRules::class,
+    Commands\Cronjobs\CronjobExecute::class,
+    Commands\Cronjobs\CronjobExecute::class,
+    Commands\Cronjobs\CronjobList::class,
+    Commands\Cronjobs\CronjobList::class,
+    Commands\Cronjobs\CronjobWorker::class,
+    Commands\Cronjobs\CronjobWorker::class,
+    Commands\DB\Dump::class,
+    Commands\DB\MigrateEngine::class,
+    Commands\DB\MigrateFileFormat::class,
+    Commands\Files\Dump::class,
+    Commands\Fix\Biest7789::class,
+    Commands\Fix\Biest7866::class,
+    Commands\Fix\Biest8136::class,
+    Commands\Fix\EndTimeWeeklyRecurredEvents::class,
+    Commands\Fix\IconDimensions::class,
+    Commands\HelpContent\Migrate::class,
+    Commands\Migrate\MigrateList::class,
+    Commands\Migrate\MigrateStatus::class,
+    Commands\Migrate\Migrate::class,
+    Commands\Plugins\PluginDeactivate::class,
+    Commands\Plugins\PluginInfo::class,
+    Commands\Plugins\PluginInstall::class,
+    Commands\Plugins\PluginListMigrations::class,
+    Commands\Plugins\PluginStatusMigrations::class,
+    Commands\Plugins\PluginMigrate::class,
+    Commands\Plugins\PluginRegister::class,
+    Commands\Plugins\PluginScan::class,
+    Commands\Plugins\PluginUnregister::class,
+    Commands\Plugins\I18N\I18NDetect::class,
+    Commands\Plugins\I18N\I18NExtract::class,
+    Commands\Plugins\I18N\I18NCompile::class,
+    Commands\Resources\UpdateBookingIntervals::class,
+    Commands\SORM\DescribeModels::class,
+    Commands\Translations\VueGettextSplitTranslations::class,
+    Commands\Users\UserDelete::class,
+    Commands\Users\UserDelete::class,
+];
+$creator = function ($command) {
+    return new $command();
+};
+$application->addCommands(array_map($creator, $commands));
+$application->run();
diff --git a/cli/studip-compat.php b/cli/studip-compat.php
deleted file mode 100755
index 18b18c20d45181364681f3cace4b989bd007eed3..0000000000000000000000000000000000000000
--- a/cli/studip-compat.php
+++ /dev/null
@@ -1,203 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once 'studip_cli_env.inc.php';
-
-$opts = getopt('fhnvc', ['filenames', 'help', 'non-recursive', 'verbose', 'no-color']);
-
-if (isset($opts['h']) || isset($opts['help'])) {
-    fwrite(STDOUT, 'Stud.IP compatibility scanner - Checks plugins for common issues' . PHP_EOL);
-    fwrite(STDOUT, '================================================================' . PHP_EOL);
-    fwrite(STDOUT, 'Usage: ' . basename(__FILE__) . ' [OPTION] [VERSION] [FOLDER] ..' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    fwrite(STDOUT, '[VERSION] is optional, if not given all checks are applied.' . PHP_EOL);
-    fwrite(STDOUT, '[FOLDER] will default to the plugins_packages folder.' . PHP_EOL);
-    fwrite(STDOUT, 'Supply as many folders as you need.' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    fwrite(STDOUT, 'Options:' . PHP_EOL);
-    fwrite(STDOUT, ' -h, --help            Display this help' . PHP_EOL);
-    fwrite(STDOUT, ' -f, --filenames       Display only filenames' . PHP_EOL);
-    fwrite(STDOUT, ' -n, --non-recursive   Do not scan recursively into subfolders' . PHP_EOL);
-    fwrite(STDOUT, ' -c, --no-color        Do not use colors for output' . PHP_EOL);
-    fwrite(STDOUT, ' -v, --verbose         Print additional information' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    exit(0);
-}
-
-// Reduce arguments by options (this is far from perfect)
-$args = $_SERVER['argv'];
-$arg_stop = array_search('--', $args);
-if ($arg_stop !== false) {
-    $args = array_slice($args, $arg_stop + 1);
-} elseif (count($opts)) {
-    $args = array_slice($args, 1 + count($opts));
-} else {
-    $args = array_slice($args, 1);
-}
-
-$verbose        = isset($opts['v']) || isset($opts['verbose']);
-$only_filenames = isset($opts['f']) || isset($opts['filenames']);
-$recursive      = !(isset($opts['n']) || isset($opts['non-recursive']));
-$no_colors      = isset($opts['c']) || isset($opts['no-color']) || !stream_isatty(STDOUT);
-$version        = null;
-$folders        = array_values($args) ?: [];
-
-if (count($folders) > 0 && preg_match('/^\d+\.\d+$/', $folders[0])) {
-    $version = array_shift($folders);
-}
-
-// Prepare logging mechanism
-$log = function ($message) use ($no_colors) {
-    $ansi = [
-        'off'        => 0,
-        'bold'       => 1,
-        'italic'     => 3,
-        'underline'  => 4,
-        'blink'      => 5,
-        'inverse'    => 7,
-        'hidden'     => 8,
-        'black'      => 30,
-        'red'        => 31,
-        'green'      => 32,
-        'yellow'     => 33,
-        'blue'       => 34,
-        'magenta'    => 35,
-        'cyan'       => 36,
-        'white'      => 37,
-        'black_bg'   => 40,
-        'red_bg'     => 41,
-        'green_bg'   => 42,
-        'yellow_bg'  => 43,
-        'blue_bg'    => 44,
-        'magenta_bg' => 45,
-        'cyan_bg'    => 46,
-        'white_bg'   => 47
-    ];
-
-    $message = trim($message);
-
-    if ($message) {
-        $args = array_slice(func_get_args(), 1);
-        $message = vsprintf($message . "\n", $args);
-
-        $ansi_codes = implode('|', array_keys($ansi));
-        if (preg_match_all('/#\{((?:(?:' . $ansi_codes . '),?)+):(.+?)\}/s', $message, $matches, PREG_SET_ORDER)) {
-            foreach ($matches as $match) {
-                $chunk = '';
-                if (!$no_colors) {
-                    $codes = explode(',', $match[1]);
-                    foreach ($codes as $code) {
-                        $chunk .= "\033[{$ansi[$code]}m";
-                    }
-                }
-                $chunk .= $match[2];
-                if (!$no_colors) {
-                    $chunk .= "\033[{$ansi[off]}m";
-                }
-
-                $message = str_replace($match[0], $chunk, $message);
-            }
-        }
-
-        print $message;
-    }
-};
-$log_if = function ($condition, $message) use ($log) {
-    if ($condition) {
-        call_user_func_array($log, array_slice(func_get_args(), 1));
-    }
-};
-
-// Reduces filename by base path and plugin folder
-$reduce = function ($folder) {
-    $folder = str_replace($GLOBALS['STUDIP_BASE_PATH'] . '/', '', $folder);
-    $folder = str_replace('public/plugins_packages/', '', $folder);
-    return $folder;
-};
-
-// Get rules
-if (!$version) {
-    $rules = [];
-    foreach (glob(__DIR__ . '/compatibility-rules/*.php') as $file) {
-        $version_rules = require $file;
-        $rules = array_merge($rules, $version_rules);
-    }
-} elseif (!file_exists(__DIR__ . "/compatibility-rules/studip-{$version}.php")) {
-    $log('#{red:No rules defined for Stud.IP version %s}', $version);
-    die;
-} else {
-    $rules = require __DIR__ . "/compatibility-rules/studip-{$version}.php";
-}
-
-// Prepare folders
-if (count($folders) === 0) {
-    $folders = rtrim($GLOBALS['STUDIP_BASE_PATH'], '/') . '/public/plugins_packages';
-    $folders = glob($folders . '/*/*');
-}
-$folders = array_unique($folders);
-
-$checkRule = function ($rule, $contents) {
-    if ($rule[0] === '/' && $rule[strlen($rule) - 1] === '/') {
-        return (bool) preg_match("{$rule}s", $contents);
-    }
-
-    return strpos($contents, strtolower($rule)) > 0;
-};
-
-// Main checker
-$check = function ($filename) use ($checkRule, $rules) {
-    $errors = [];
-
-    $contents = strtolower(file_get_contents($filename));
-    foreach ($rules as $needle => $suggestion) {
-        if ($checkRule($needle, $contents)) {
-            $errors[$needle] = $suggestion;
-        }
-    }
-    return $errors;
-};
-
-// Engage
-foreach ($folders as $folder) {
-    if (!file_exists($folder) || !is_dir($folder)) {
-        $log_if($verbose, 'Skipping non-folder arg #{red:%s}', $folder);
-        continue;
-    }
-
-    $log_if($verbose && !$only_filenames, '#{green:Scanning} %s', $reduce($folder));
-    if ($recursive) {
-        $iterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS);
-        $iterator = new RecursiveIteratorIterator($iterator);
-    } else {
-        $iterator = new DirectoryIterator($folder);
-    }
-    $regexp_iterator = new RegexIterator($iterator, '/.*\.(?:php|tpl|inc|js)$/', RecursiveRegexIterator::MATCH);
-
-    $issues = [];
-
-    foreach ($regexp_iterator as $file) {
-        $filename = $file->getPathName();
-        $log_if($verbose, "Checking #{magenta:%s}", $filename);
-        if ($errors = $check($filename)) {
-            $issues[$filename] = $errors;
-        }
-    }
-
-    if (count($issues) > 0) {
-        $issue_count = array_sum(array_map('count', $issues));
-        $message = count($issues) === 1
-                 ? '#{red:%u issue found in} #{red,bold:%s}'
-                 : '#{red:%u issues found in} #{red,bold:%s}';
-        $log_if(!$only_filenames, $message, $issue_count, $reduce($folder));
-
-        foreach ($issues as $filename => $errors) {
-            if ($only_filenames) {
-                $log($filename);
-            } else {
-                $log('> File #{green,bold:%s}', $reduce($filename));
-                foreach ($errors as $needle => $suggestion) {
-                    $log('- #{cyan:%s} -> %s', $needle, $suggestion ?: '#{red:No suggestion available}');
-                }
-            }
-        }
-    }
-}
diff --git a/cli/studip_cli_env.inc.php b/cli/studip_cli_env.inc.php
index a0e33cdede50803803c00f2387750377fbf32a14..a5ef8f46dd6a10e9978f82e362226446a8eb3ef1 100644
--- a/cli/studip_cli_env.inc.php
+++ b/cli/studip_cli_env.inc.php
@@ -48,7 +48,7 @@ function parse_msg_to_clean_text($long_msg,$separator="§") {
     return join("\n", $ret);
 }
 
-$STUDIP_BASE_PATH = realpath( dirname(__FILE__) . '/..');
+$STUDIP_BASE_PATH = realpath( __DIR__ . '/..');
 $include_path = get_include_path();
 $include_path .= PATH_SEPARATOR . $STUDIP_BASE_PATH . DIRECTORY_SEPARATOR . 'public';
 set_include_path($include_path);
diff --git a/cli/tic_5671_scan.php b/cli/tic_5671_scan.php
deleted file mode 100755
index 07fa5c2b943caa5ab91cd7f2bb5f2fc3b34769ce..0000000000000000000000000000000000000000
--- a/cli/tic_5671_scan.php
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/env php
-<?php
-require_once 'studip_cli_env.inc.php';
-
-$opts = getopt('fhnosv', ['filenames', 'help', 'non-recursive', 'occurences', 'matches', 'verbose']);
-
-if (isset($opts['h']) || isset($opts['help'])) {
-    fwrite(STDOUT, 'TIC 5671 Scanner - Scans files for occurences of globalized config items' . PHP_EOL);
-    fwrite(STDOUT, '========================================================================' . PHP_EOL);
-    fwrite(STDOUT, 'Usage: ' . basename(__FILE__) . ' [OPTION] [FOLDER] [FOLDER2] ..' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    fwrite(STDOUT, '[FOLDER] will default to Stud.IP base folder.' . PHP_EOL);
-    fwrite(STDOUT, 'Supply many folders if you need to.' . PHP_EOL);
-    fwrite(STDOUT, 'You may pass the special value of "plugins" to scan the plugin folder.' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    fwrite(STDOUT, 'Options:' . PHP_EOL);
-    fwrite(STDOUT, ' -h, --help               Display this help' . PHP_EOL);
-    fwrite(STDOUT, ' -f, --filenames          Display only filenames (excludes -m and -o)' . PHP_EOL);
-    fwrite(STDOUT, ' -n, --non-recursive      Do not scan recursively into subfolders' . PHP_EOL);
-    fwrite(STDOUT, ' -m, --matches            Show matched config variables' . PHP_EOL);
-    fwrite(STDOUT, ' -o, --occurences         Display occurences in files (implies -s)' . PHP_EOL);
-    fwrite(STDOUT, ' -v, --verbose            Print additional information' . PHP_EOL);
-    fwrite(STDOUT, PHP_EOL);
-    exit(0);
-}
-
-// Reduce arguments by options (this is far from perfect)
-$args = $_SERVER['argv'];
-$arg_stop = array_search('--', $args);
-if ($arg_stop !== false) {
-    $args = array_slice($args, $arg_stop + 1);
-} elseif (count($opts)) {
-    $args = array_slice($args, 1 + count($opts));
-} else {
-    $args = array_slice($args, 1);
-}
-
-$verbose         = isset($opts['v']) || isset($opts['verbose']);
-$only_filenames  = isset($opts['f']) || isset($opts['filenames']);
-$show_occurences = $verbose || isset($opts['o']) || isset($opts['occurences']);
-$show_matches    = $show_occurences || isset($opts['m']) || isset($opts['matches']);
-$recursive       = !(isset($opts['n']) || isset($opts['recursive']));
-$folders         = $args ?: [$GLOBALS['STUDIP_BASE_PATH']];
-
-// Prepare logging mechanism
-$log = function ($message) {
-    $ansi = [
-        'off'        => 0,
-        'bold'       => 1,
-        'italic'     => 3,
-        'underline'  => 4,
-        'blink'      => 5,
-        'inverse'    => 7,
-        'hidden'     => 8,
-        'black'      => 30,
-        'red'        => 31,
-        'green'      => 32,
-        'yellow'     => 33,
-        'blue'       => 34,
-        'magenta'    => 35,
-        'cyan'       => 36,
-        'white'      => 37,
-        'black_bg'   => 40,
-        'red_bg'     => 41,
-        'green_bg'   => 42,
-        'yellow_bg'  => 43,
-        'blue_bg'    => 44,
-        'magenta_bg' => 45,
-        'cyan_bg'    => 46,
-        'white_bg'   => 47
-    ];
-    
-    $message = trim($message);
-
-    if ($message) {
-        $ansi_codes = implode('|', array_keys($ansi));
-        if (preg_match_all('/#\{((?:(?:' . $ansi_codes . '),?)+):(.+?)\}/s', $message, $matches, PREG_SET_ORDER)) {
-            foreach ($matches as $match) {
-                $chunk = '';
-                $codes = explode(',', $match[1]);
-                foreach ($codes as $code) {
-                    $chunk .= "\033[{$ansi[$code]}m";
-                }
-                $chunk .= $match[2] . "\033[{$ansi[off]}m";
-                
-                $message = str_replace($match[0], $chunk, $message);
-            }
-        }
-        
-        $args = array_slice(func_get_args(), 1);
-        vprintf($message . "\n", $args);
-    }
-};
-$log_if = function ($condition, $message) use ($log) {
-    if ($condition) {
-        call_user_func_array($log, array_slice(func_get_args(), 1));
-    }
-};
-
-// Prepare line highlighter
-$highlight = function ($content, $variable) {
-    $lines = explode("\n", $content);
-    
-    $result = [];
-    foreach ($lines as $index => $line) {
-        if (mb_strpos($line, $variable) === false) {
-            continue;
-        }
-        $result[$index + 1] = $line;
-    }
-
-    if (!$result) {
-        return '';
-    }
-
-    $max = max(array_map('mb_strlen', array_keys($result)));
-
-    foreach ($result as $index => $line) {
-        $result[$index] = sprintf('#{yellow:%0' . $max . 'u}: %s', $index, str_replace($variable, "#{yellow_bg,black:$variable}", $line));
-    }
-    
-    return implode("\n", $result);
-};
-
-// Prepare folders
-foreach ($folders as $index => $folder) {
-    if ($folder === 'plugins') {
-        $folders[$index] = $GLOBALS['STUDIP_BASE_PATH'] . '/public/plugins_packages/';
-    }
-}
-$folders = array_unique($folders);
-
-// Prepare regexp from regexp
-$config = Config::get()->getFields('global');
-$quoted = array_map(function ($item) { return preg_quote($item, '/'); }, $config);
-$regexp = '/\$(?:GLOBALS\[["\']?)?(' . implode('|', $quoted) . ')\b/S';
-
-// Engage
-foreach ($folders as $folder) {
-    if (!file_exists($folder) || !is_dir($folder)) {
-        $log_if($verbose, 'Skipping non-folder arg #{red:%s}', $folder);
-        continue;
-    }
-    $log_if($verbose, 'Scanning "%s"', $folder);
-    if ($recursive) {
-        $iterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS);
-        $iterator = new RecursiveIteratorIterator($iterator);
-    } else {
-        $iterator = new DirectoryIterator($folder);
-    }
-    $regexp_iterator = new RegexIterator($iterator, '/.*\.(?:php|tpl|inc)$/', RecursiveRegexIterator::MATCH);
-
-    foreach ($regexp_iterator as $file) {
-        $filename = $file->getPathName();
-        $contents = file_get_contents($filename);
-        $log_if($verbose, "Checking #{magenta:%s}", $filename);
-        if ($matched = preg_match_all($regexp, $contents, $matches)) {
-            if ($only_filenames) {
-                $log($filename);
-            } else {
-                $log('%u matched variable(s) in #{green,bold:%s}', $matched, $filename);
-                if ($show_matches) {
-                    $variables = array_unique($matches[1]);
-                    foreach ($variables as $variable) {
-                        $log('>> #{cyan:%s}', $variable);
-                        $log_if($show_occurences, $highlight($contents, $variable));
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/cli/update-resource-booking-intervals.php b/cli/update-resource-booking-intervals.php
deleted file mode 100644
index 50c6e4b59277613b0e539ace9a2d18fb8e19a104..0000000000000000000000000000000000000000
--- a/cli/update-resource-booking-intervals.php
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env php
-<?php
-
-
-require_once(__DIR__ . '/studip_cli_env.inc.php');
-
-
-$keep_exceptions = true;
-
-$options = getopt('h', ['remove-exceptions']);
-
-if (array_key_exists('h', $options)) {
-    echo("Usage:\tupdate-resource-booking-intervals.php [--remove-exceptions]\n");
-    echo("\tIf --remove-exceptions is set, exceptions for a booking with repetitions\n");
-    echo("\twill be removed. By default, they are kept.\n");
-    exit(0);
-}
-
-if (array_key_exists('remove-exceptions', $options)) {
-    $keep_exceptions = false;
-    echo("Exceptions in bookings with repetitions will be removed!\n");
-}
-
-$bookings = ResourceBooking::findBySql('TRUE');
-if (!$bookings) {
-    echo("There are no bookings in your database! Nothing to do!\n");
-    exit(0);
-}
-foreach ($bookings as $booking) {
-    $booking->updateIntervals($keep_exceptions);
-}
-
-echo("End of script. The resource_booking_intervals table is up to date again!\n");
diff --git a/cli/vue-gettext-split-translations.php b/cli/vue-gettext-split-translations.php
deleted file mode 100755
index 0ff09d10cf4cc365d9fd66ffc09d9bfc9afa45e3..0000000000000000000000000000000000000000
--- a/cli/vue-gettext-split-translations.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-$translationsFile = realpath(__DIR__ . '/../resources/locales/translations.json');
-if (!file_exists($translationsFile)) {
-    fwrite(STDERR, "Could not find translations in '" . $translationsFile . "'.\n");
-    exit(1);
-}
-
-$file = file_get_contents($translationsFile);
-$json = json_decode($file, true);
-
-foreach ($json as $lang => $content) {
-    $langFile = realpath(__DIR__ . '/../resources/locales/') . '/' . $lang . '.json';
-    file_put_contents($langFile, json_encode($content));
-}
diff --git a/composer.json b/composer.json
index 50b3f8a6ec937cd12206e702991703900f633589..ba9f55ed8f91f27725ee503617571e8e73839e02 100644
--- a/composer.json
+++ b/composer.json
@@ -45,6 +45,8 @@
         "opis/json-schema": "^1.0",
         "slim/psr7": "1.4",
         "slim/slim": "4.7.1",
-        "php-di/php-di": "6.3.4"
+        "php-di/php-di": "6.3.4",
+        "symfony/console": "5.3.6",
+        "symfony/process": "^5.4"
     }
 }
diff --git a/composer.lock b/composer.lock
index 440bb705a837ef291243ddaeb13a2e385cdf13cf..83fc9e51262501f07a3686237b78328422552b95 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "98af1effd6c2da72cc51ac552e851451",
+    "content-hash": "59006c71d43d6f32f0445636c803208d",
     "packages": [
         {
             "name": "algo26-matthias/idna-convert",
@@ -2254,41 +2254,61 @@
             "time": "2018-09-13T19:25:26+00:00"
         },
         {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.18.1",
+            "name": "symfony/console",
+            "version": "v5.3.6",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
+                "url": "https://github.com/symfony/console.git",
+                "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
-                "reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
+                "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
+                "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php73": "^1.8",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/service-contracts": "^1.1|^2",
+                "symfony/string": "^5.1"
+            },
+            "conflict": {
+                "psr/log": ">=3",
+                "symfony/dependency-injection": "<4.4",
+                "symfony/dotenv": "<5.1",
+                "symfony/event-dispatcher": "<4.4",
+                "symfony/lock": "<4.4",
+                "symfony/process": "<4.4"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0|2.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2",
+                "symfony/config": "^4.4|^5.0",
+                "symfony/dependency-injection": "^4.4|^5.0",
+                "symfony/event-dispatcher": "^4.4|^5.0",
+                "symfony/lock": "^4.4|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "symfony/var-dumper": "^4.4|^5.0"
             },
             "suggest": {
-                "ext-ctype": "For best performance"
+                "psr/log": "For using the console logger",
+                "symfony/event-dispatcher": "",
+                "symfony/lock": "",
+                "symfony/process": ""
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.18-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
+                    "Symfony\\Component\\Console\\": ""
                 },
-                "files": [
-                    "bootstrap.php"
+                "exclude-from-classmap": [
+                    "/Tests/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -2297,24 +2317,24 @@
             ],
             "authors": [
                 {
-                    "name": "Gert de Pagter",
-                    "email": "BackEndTea@gmail.com"
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for ctype functions",
+            "description": "Eases the creation of beautiful and testable command line interfaces",
             "homepage": "https://symfony.com",
             "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
+                "cli",
+                "command line",
+                "console",
+                "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.18.0"
+                "source": "https://github.com/symfony/console/tree/v5.3.6"
             },
             "funding": [
                 {
@@ -2330,40 +2350,38 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-07-14T12:35:20+00:00"
+            "time": "2021-07-27T19:10:22+00:00"
         },
         {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.10.0",
+            "name": "symfony/deprecation-contracts",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+                "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
+                "php": ">=7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-main": "2.4-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
-                },
                 "files": [
-                    "bootstrap.php"
+                    "function.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -2380,37 +2398,46 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for the Mbstring extension",
+            "description": "A generic function and convention to trigger deprecation notices",
             "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "mbstring",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/master"
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
             },
-            "time": "2018-09-21T13:07:52+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-03-23T23:28:01+00:00"
         },
         {
-            "name": "symfony/polyfill-php56",
+            "name": "symfony/polyfill-ctype",
             "version": "v1.18.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-php56.git",
-                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011"
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/13df84e91cd168f247c2f2ec82cc0fa24901c011",
-                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
+                "reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3",
-                "symfony/polyfill-util": "~1.0"
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
             },
             "type": "library",
             "extra": {
@@ -2424,7 +2451,7 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Php56\\": ""
+                    "Symfony\\Polyfill\\Ctype\\": ""
                 },
                 "files": [
                     "bootstrap.php"
@@ -2436,24 +2463,24 @@
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
+            "description": "Symfony polyfill for ctype functions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
+                "ctype",
                 "polyfill",
-                "portable",
-                "shim"
+                "portable"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php56/tree/v1.18.1"
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.18.0"
             },
             "funding": [
                 {
@@ -2472,22 +2499,25 @@
             "time": "2020-07-14T12:35:20+00:00"
         },
         {
-            "name": "symfony/polyfill-php80",
+            "name": "symfony/polyfill-intl-grapheme",
             "version": "v1.23.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0"
+                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+                "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0",
-                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab",
+                "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.1"
             },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
@@ -2500,13 +2530,10 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Php80\\": ""
+                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
                 },
                 "files": [
                     "bootstrap.php"
-                ],
-                "classmap": [
-                    "Resources/stubs"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -2514,10 +2541,6 @@
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Ion Bazan",
-                    "email": "ion.bazan@gmail.com"
-                },
                 {
                     "name": "Nicolas Grekas",
                     "email": "p@tchwork.com"
@@ -2527,16 +2550,18 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "description": "Symfony polyfill for intl's grapheme_* functions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
+                "grapheme",
+                "intl",
                 "polyfill",
                 "portable",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0"
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0"
             },
             "funding": [
                 {
@@ -2552,29 +2577,32 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-02-19T12:13:01+00:00"
+            "time": "2021-05-27T09:17:38+00:00"
         },
         {
-            "name": "symfony/polyfill-util",
-            "version": "v1.18.1",
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.23.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-util.git",
-                "reference": "46b910c71e9828f8ec2aa7a0314de1130d9b295a"
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/46b910c71e9828f8ec2aa7a0314de1130d9b295a",
-                "reference": "46b910c71e9828f8ec2aa7a0314de1130d9b295a",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
+                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
+                "php": ">=7.1"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.18-dev"
+                    "dev-main": "1.23-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
@@ -2583,8 +2611,14 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Util\\": ""
-                }
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -2600,16 +2634,18 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony utilities for portability of PHP codes",
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
             "homepage": "https://symfony.com",
             "keywords": [
-                "compat",
                 "compatibility",
+                "intl",
+                "normalizer",
                 "polyfill",
+                "portable",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-util/tree/v1.18.0"
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0"
             },
             "funding": [
                 {
@@ -2625,42 +2661,40 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-07-14T12:35:20+00:00"
+            "time": "2021-02-19T12:13:01+00:00"
         },
         {
-            "name": "symfony/yaml",
-            "version": "v3.4.46",
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.10.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/yaml.git",
-                "reference": "88289caa3c166321883f67fe5130188ebbb47094"
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094",
-                "reference": "88289caa3c166321883f67fe5130188ebbb47094",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "symfony/polyfill-ctype": "~1.8"
-            },
-            "conflict": {
-                "symfony/console": "<3.4"
-            },
-            "require-dev": {
-                "symfony/console": "~3.4|~4.0"
+                "php": ">=5.3.3"
             },
             "suggest": {
-                "symfony/console": "For validating YAML files using the lint command"
+                "ext-mbstring": "For best performance"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Component\\Yaml\\": ""
+                    "Symfony\\Polyfill\\Mbstring\\": ""
                 },
-                "exclude-from-classmap": [
-                    "/Tests/"
+                "files": [
+                    "bootstrap.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -2669,18 +2703,88 @@
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Yaml Component",
+            "description": "Symfony polyfill for the Mbstring extension",
             "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
             "support": {
-                "source": "https://github.com/symfony/yaml/tree/v3.4.46"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/master"
+            },
+            "time": "2018-09-21T13:07:52+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php56",
+            "version": "v1.18.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php56.git",
+                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/13df84e91cd168f247c2f2ec82cc0fa24901c011",
+                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/polyfill-util": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php56\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php56/tree/v1.18.1"
             },
             "funding": [
                 {
@@ -2696,104 +2800,124 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-10-24T10:57:07+00:00"
+            "time": "2020-07-14T12:35:20+00:00"
         },
         {
-            "name": "tecnickcom/tcpdf",
-            "version": "6.3.5",
+            "name": "symfony/polyfill-php73",
+            "version": "v1.23.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/tecnickcom/TCPDF.git",
-                "reference": "19a535eaa7fb1c1cac499109deeb1a7a201b4549"
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/19a535eaa7fb1c1cac499109deeb1a7a201b4549",
-                "reference": "19a535eaa7fb1c1cac499109deeb1a7a201b4549",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010",
+                "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0"
+                "php": ">=7.1"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.23-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
             "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
                 "classmap": [
-                    "config",
-                    "include",
-                    "tcpdf.php",
-                    "tcpdf_parser.php",
-                    "tcpdf_import.php",
-                    "tcpdf_barcodes_1d.php",
-                    "tcpdf_barcodes_2d.php",
-                    "include/tcpdf_colors.php",
-                    "include/tcpdf_filters.php",
-                    "include/tcpdf_font_data.php",
-                    "include/tcpdf_fonts.php",
-                    "include/tcpdf_images.php",
-                    "include/tcpdf_static.php",
-                    "include/barcodes/datamatrix.php",
-                    "include/barcodes/pdf417.php",
-                    "include/barcodes/qrcode.php"
+                    "Resources/stubs"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "LGPL-3.0-only"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Nicola Asuni",
-                    "email": "info@tecnick.com",
-                    "role": "lead"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
-            "homepage": "http://www.tcpdf.org/",
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
             "keywords": [
-                "PDFD32000-2008",
-                "TCPDF",
-                "barcodes",
-                "datamatrix",
-                "pdf",
-                "pdf417",
-                "qrcode"
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
             ],
             "support": {
-                "issues": "https://github.com/tecnickcom/TCPDF/issues",
-                "source": "https://github.com/tecnickcom/TCPDF/tree/6.3.5"
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0"
             },
-            "time": "2020-02-14T14:20:12+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-02-19T12:13:01+00:00"
         },
         {
-            "name": "tuupola/callable-handler",
-            "version": "1.1.0",
+            "name": "symfony/polyfill-php80",
+            "version": "v1.23.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/tuupola/callable-handler.git",
-                "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c"
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/tuupola/callable-handler/zipball/0bc7b88630ca753de9aba8f411046856f5ca6f8c",
-                "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0",
+                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1|^8.0",
-                "psr/http-server-middleware": "^1.0"
-            },
-            "require-dev": {
-                "overtrue/phplint": "^1.0",
-                "phpunit/phpunit": "^7.0|^8.0|^9.0",
-                "squizlabs/php_codesniffer": "^3.2",
-                "tuupola/http-factory": "^0.4.0|^1.0",
-                "zendframework/zend-diactoros": "^1.6.0|^2.0"
+                "php": ">=7.1"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.23-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
             "autoload": {
                 "psr-4": {
-                    "Tuupola\\Middleware\\": "src"
-                }
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -2801,65 +2925,75 @@
             ],
             "authors": [
                 {
-                    "name": "Mika Tuupola",
-                    "email": "tuupola@appelsiini.net",
-                    "homepage": "https://appelsiini.net/",
-                    "role": "Developer"
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Compatibility layer for PSR-7 double pass and PSR-15 middlewares.",
-            "homepage": "https://github.com/tuupola/callable-handler",
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
             "keywords": [
-                "middleware",
-                "psr-15",
-                "psr-7"
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
             ],
             "support": {
-                "issues": "https://github.com/tuupola/callable-handler/issues",
-                "source": "https://github.com/tuupola/callable-handler/tree/1.1.0"
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0"
             },
             "funding": [
                 {
-                    "url": "https://github.com/tuupola",
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
                     "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
                 }
             ],
-            "time": "2020-09-09T08:31:54+00:00"
+            "time": "2021-02-19T12:13:01+00:00"
         },
         {
-            "name": "tuupola/cors-middleware",
-            "version": "1.2.1",
+            "name": "symfony/polyfill-util",
+            "version": "v1.18.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/tuupola/cors-middleware.git",
-                "reference": "4f085d11f349e83d18f1eb5802551353b2b093a3"
+                "url": "https://github.com/symfony/polyfill-util.git",
+                "reference": "46b910c71e9828f8ec2aa7a0314de1130d9b295a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/4f085d11f349e83d18f1eb5802551353b2b093a3",
-                "reference": "4f085d11f349e83d18f1eb5802551353b2b093a3",
+                "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/46b910c71e9828f8ec2aa7a0314de1130d9b295a",
+                "reference": "46b910c71e9828f8ec2aa7a0314de1130d9b295a",
                 "shasum": ""
             },
             "require": {
-                "neomerx/cors-psr7": "^1.0.4",
-                "php": "^7.1|^8.0",
-                "psr/http-message": "^1.0.1",
-                "psr/http-server-middleware": "^1.0",
-                "tuupola/callable-handler": "^1.0",
-                "tuupola/http-factory": "^1.0.2"
-            },
-            "require-dev": {
-                "equip/dispatch": "^2.0",
-                "overtrue/phplint": "^1.0",
-                "phpstan/phpstan": "^0.12.42",
-                "phpunit/phpunit": "^7.0|^8.0|^9.0",
-                "squizlabs/php_codesniffer": "^3.5",
-                "zendframework/zend-diactoros": "^1.0|^2.0"
+                "php": ">=5.3.3"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
             "autoload": {
                 "psr-4": {
-                    "Tuupola\\Middleware\\": "src"
+                    "Symfony\\Polyfill\\Util\\": ""
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -2868,67 +3002,67 @@
             ],
             "authors": [
                 {
-                    "name": "Mika Tuupola",
-                    "email": "tuupola@appelsiini.net",
-                    "homepage": "https://appelsiini.net/",
-                    "role": "Developer"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "PSR-7 and PSR-15 CORS middleware",
-            "homepage": "https://github.com/tuupola/cors-middleware",
+            "description": "Symfony utilities for portability of PHP codes",
+            "homepage": "https://symfony.com",
             "keywords": [
-                "cors",
-                "middleware",
-                "psr-15",
-                "psr-7"
+                "compat",
+                "compatibility",
+                "polyfill",
+                "shim"
             ],
             "support": {
-                "issues": "https://github.com/tuupola/cors-middleware/issues",
-                "source": "https://github.com/tuupola/cors-middleware/tree/1.2.1"
+                "source": "https://github.com/symfony/polyfill-util/tree/v1.18.0"
             },
             "funding": [
                 {
-                    "url": "https://github.com/tuupola",
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
                     "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
                 }
             ],
-            "time": "2020-10-29T11:01:06+00:00"
+            "time": "2020-07-14T12:35:20+00:00"
         },
         {
-            "name": "tuupola/http-factory",
-            "version": "1.3.0",
+            "name": "symfony/process",
+            "version": "v5.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/tuupola/http-factory.git",
-                "reference": "aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac"
+                "url": "https://github.com/symfony/process.git",
+                "reference": "5be20b3830f726e019162b26223110c8f47cf274"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/tuupola/http-factory/zipball/aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac",
-                "reference": "aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac",
+                "url": "https://api.github.com/repos/symfony/process/zipball/5be20b3830f726e019162b26223110c8f47cf274",
+                "reference": "5be20b3830f726e019162b26223110c8f47cf274",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1|^8.0",
-                "psr/http-factory": "^1.0"
-            },
-            "conflict": {
-                "nyholm/psr7": "<1.0"
-            },
-            "provide": {
-                "psr/http-factory-implementation": "^1.0"
-            },
-            "require-dev": {
-                "http-interop/http-factory-tests": "^0.7.0",
-                "overtrue/phplint": "^1.0",
-                "phpunit/phpunit": "^7.0|^8.0|^9.0",
-                "squizlabs/php_codesniffer": "^3.0"
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.16"
             },
             "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Tuupola\\Http\\Factory\\": "src"
-                }
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -2936,61 +3070,69 @@
             ],
             "authors": [
                 {
-                    "name": "Mika Tuupola",
-                    "email": "tuupola@appelsiini.net",
-                    "homepage": "https://appelsiini.net/",
-                    "role": "Developer"
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Lightweight autodiscovering PSR-17 HTTP factories",
-            "homepage": "https://github.com/tuupola/http-factory",
-            "keywords": [
-                "http",
-                "psr-17",
-                "psr-7"
-            ],
+            "description": "Executes commands in sub-processes",
+            "homepage": "https://symfony.com",
             "support": {
-                "issues": "https://github.com/tuupola/http-factory/issues",
-                "source": "https://github.com/tuupola/http-factory/tree/1.3.0"
+                "source": "https://github.com/symfony/process/tree/v5.4.0"
             },
             "funding": [
                 {
-                    "url": "https://github.com/tuupola",
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
                     "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
                 }
             ],
-            "time": "2020-10-01T07:46:32+00:00"
-        }
-    ],
-    "packages-dev": [
+            "time": "2021-11-28T15:25:38+00:00"
+        },
         {
-            "name": "adlawson/vfs",
-            "version": "0.12.1",
+            "name": "symfony/service-contracts",
+            "version": "v2.2.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/adlawson/php-vfs.git",
-                "reference": "e955034419d6a8f92c9a8ea2e626eeed96b41095"
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/adlawson/php-vfs/zipball/e955034419d6a8f92c9a8ea2e626eeed96b41095",
-                "reference": "e955034419d6a8f92c9a8ea2e626eeed96b41095",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
+                "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5",
-                "psr/log": "^1.0"
+                "php": ">=7.2.5",
+                "psr/container": "^1.0"
             },
-            "require-dev": {
-                "adlawson/timezone": "^1.0",
-                "fabpot/php-cs-fixer": "^1.9",
-                "mockery/mockery": "^0.9",
-                "phpunit/phpunit": "^4.7"
+            "suggest": {
+                "symfony/service-implementation": ""
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
             "autoload": {
                 "psr-4": {
-                    "Vfs\\": "src/"
+                    "Symfony\\Contracts\\Service\\": ""
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -2999,66 +3141,82 @@
             ],
             "authors": [
                 {
-                    "name": "Andrew Lawson",
-                    "homepage": "http://adlawson.com"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Virtual file system",
-            "homepage": "https://github.com/adlawson/php-vfs",
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
             "keywords": [
-                "dir",
-                "directory",
-                "file",
-                "fs",
-                "read",
-                "stream",
-                "system",
-                "virtual",
-                "wrapper",
-                "write"
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
             ],
             "support": {
-                "issues": "https://github.com/adlawson/php-vfs/issues",
-                "source": "https://github.com/adlawson/php-vfs/tree/develop"
+                "source": "https://github.com/symfony/service-contracts/tree/master"
             },
-            "time": "2016-02-20T12:46:01+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-09-07T11:33:47+00:00"
         },
         {
-            "name": "behat/gherkin",
-            "version": "v4.8.0",
+            "name": "symfony/string",
+            "version": "v5.3.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Behat/Gherkin.git",
-                "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd"
+                "url": "https://github.com/symfony/string.git",
+                "reference": "0732e97e41c0a590f77e231afc16a327375d50b0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2391482cd003dfdc36b679b27e9f5326bd656acd",
-                "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd",
+                "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0",
+                "reference": "0732e97e41c0a590f77e231afc16a327375d50b0",
                 "shasum": ""
             },
             "require": {
-                "php": "~7.2|~8.0"
+                "php": ">=7.2.5",
+                "symfony/polyfill-ctype": "~1.8",
+                "symfony/polyfill-intl-grapheme": "~1.0",
+                "symfony/polyfill-intl-normalizer": "~1.0",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php80": "~1.15"
             },
             "require-dev": {
-                "cucumber/cucumber": "dev-gherkin-16.0.0",
-                "phpunit/phpunit": "~8|~9",
-                "symfony/phpunit-bridge": "~3|~4|~5",
-                "symfony/yaml": "~3|~4|~5"
-            },
-            "suggest": {
-                "symfony/yaml": "If you want to parse features, represented in YAML files"
+                "symfony/error-handler": "^4.4|^5.0",
+                "symfony/http-client": "^4.4|^5.0",
+                "symfony/translation-contracts": "^1.1|^2",
+                "symfony/var-exporter": "^4.4|^5.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.4-dev"
-                }
-            },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Gherkin": "src/"
-                }
+                "psr-4": {
+                    "Symfony\\Component\\String\\": ""
+                },
+                "files": [
+                    "Resources/functions.php"
+                ],
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3066,49 +3224,78 @@
             ],
             "authors": [
                 {
-                    "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Gherkin DSL parser for PHP",
-            "homepage": "http://behat.org/",
+            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+            "homepage": "https://symfony.com",
             "keywords": [
-                "BDD",
-                "Behat",
-                "Cucumber",
-                "DSL",
-                "gherkin",
-                "parser"
+                "grapheme",
+                "i18n",
+                "string",
+                "unicode",
+                "utf-8",
+                "utf8"
             ],
             "support": {
-                "issues": "https://github.com/Behat/Gherkin/issues",
-                "source": "https://github.com/Behat/Gherkin/tree/v4.8.0"
+                "source": "https://github.com/symfony/string/tree/v5.3.2"
             },
-            "time": "2021-02-04T12:44:21+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-06-06T09:51:56+00:00"
         },
         {
-            "name": "camspiers/json-pretty",
-            "version": "1.0.2",
+            "name": "symfony/yaml",
+            "version": "v3.4.46",
             "source": {
                 "type": "git",
-                "url": "https://github.com/camspiers/json-pretty.git",
-                "reference": "17be37cb83af8014658da48fa0012604179039a7"
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "88289caa3c166321883f67fe5130188ebbb47094"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/camspiers/json-pretty/zipball/17be37cb83af8014658da48fa0012604179039a7",
-                "reference": "17be37cb83af8014658da48fa0012604179039a7",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094",
+                "reference": "88289caa3c166321883f67fe5130188ebbb47094",
                 "shasum": ""
             },
+            "require": {
+                "php": "^5.5.9|>=7.0.8",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "conflict": {
+                "symfony/console": "<3.4"
+            },
             "require-dev": {
-                "phpunit/phpunit": "~4.0"
+                "symfony/console": "~3.4|~4.0"
+            },
+            "suggest": {
+                "symfony/console": "For validating YAML files using the lint command"
             },
             "type": "library",
             "autoload": {
-                "psr-0": {
-                    "Camspiers": "src/"
-                }
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3116,144 +3303,130 @@
             ],
             "authors": [
                 {
-                    "name": "Cam Spiers",
-                    "email": "cameron@heyday.co.nz"
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Provides support for json pretty printing",
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
             "support": {
-                "issues": "https://github.com/camspiers/json-pretty/issues",
-                "source": "https://github.com/camspiers/json-pretty/tree/master"
+                "source": "https://github.com/symfony/yaml/tree/v3.4.46"
             },
-            "time": "2016-02-06T01:25:58+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-24T10:57:07+00:00"
         },
         {
-            "name": "clue/stream-filter",
-            "version": "v1.4.1",
+            "name": "tecnickcom/tcpdf",
+            "version": "6.3.5",
             "source": {
                 "type": "git",
-                "url": "https://github.com/clue/stream-filter.git",
-                "reference": "5a58cc30a8bd6a4eb8f856adf61dd3e013f53f71"
+                "url": "https://github.com/tecnickcom/TCPDF.git",
+                "reference": "19a535eaa7fb1c1cac499109deeb1a7a201b4549"
             },
             "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/clue/stream-filter/zipball/5a58cc30a8bd6a4eb8f856adf61dd3e013f53f71",
-                "reference": "5a58cc30a8bd6a4eb8f856adf61dd3e013f53f71",
+                "type": "zip",
+                "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/19a535eaa7fb1c1cac499109deeb1a7a201b4549",
+                "reference": "19a535eaa7fb1c1cac499109deeb1a7a201b4549",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^5.0 || ^4.8"
+                "php": ">=5.3.0"
             },
             "type": "library",
             "autoload": {
-                "psr-4": {
-                    "Clue\\StreamFilter\\": "src/"
-                },
-                "files": [
-                    "src/functions_include.php"
+                "classmap": [
+                    "config",
+                    "include",
+                    "tcpdf.php",
+                    "tcpdf_parser.php",
+                    "tcpdf_import.php",
+                    "tcpdf_barcodes_1d.php",
+                    "tcpdf_barcodes_2d.php",
+                    "include/tcpdf_colors.php",
+                    "include/tcpdf_filters.php",
+                    "include/tcpdf_font_data.php",
+                    "include/tcpdf_fonts.php",
+                    "include/tcpdf_images.php",
+                    "include/tcpdf_static.php",
+                    "include/barcodes/datamatrix.php",
+                    "include/barcodes/pdf417.php",
+                    "include/barcodes/qrcode.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "LGPL-3.0-only"
             ],
             "authors": [
                 {
-                    "name": "Christian Lück",
-                    "email": "christian@lueck.tv"
+                    "name": "Nicola Asuni",
+                    "email": "info@tecnick.com",
+                    "role": "lead"
                 }
             ],
-            "description": "A simple and modern approach to stream filtering in PHP",
-            "homepage": "https://github.com/clue/php-stream-filter",
+            "description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
+            "homepage": "http://www.tcpdf.org/",
             "keywords": [
-                "bucket brigade",
-                "callback",
-                "filter",
-                "php_user_filter",
-                "stream",
-                "stream_filter_append",
-                "stream_filter_register"
+                "PDFD32000-2008",
+                "TCPDF",
+                "barcodes",
+                "datamatrix",
+                "pdf",
+                "pdf417",
+                "qrcode"
             ],
             "support": {
-                "issues": "https://github.com/clue/stream-filter/issues",
-                "source": "https://github.com/clue/stream-filter/tree/v1.4.1"
+                "issues": "https://github.com/tecnickcom/TCPDF/issues",
+                "source": "https://github.com/tecnickcom/TCPDF/tree/6.3.5"
             },
-            "funding": [
-                {
-                    "url": "https://clue.engineering/support",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/clue",
-                    "type": "github"
-                }
-            ],
-            "time": "2019-04-09T12:31:48+00:00"
+            "time": "2020-02-14T14:20:12+00:00"
         },
         {
-            "name": "codeception/codeception",
-            "version": "4.1.21",
+            "name": "tuupola/callable-handler",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Codeception/Codeception.git",
-                "reference": "c25f20d842a7e3fa0a8e6abf0828f102c914d419"
+                "url": "https://github.com/tuupola/callable-handler.git",
+                "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c25f20d842a7e3fa0a8e6abf0828f102c914d419",
-                "reference": "c25f20d842a7e3fa0a8e6abf0828f102c914d419",
+                "url": "https://api.github.com/repos/tuupola/callable-handler/zipball/0bc7b88630ca753de9aba8f411046856f5ca6f8c",
+                "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c",
                 "shasum": ""
             },
             "require": {
-                "behat/gherkin": "^4.4.0",
-                "codeception/lib-asserts": "^1.0",
-                "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0",
-                "codeception/stub": "^2.0 | ^3.0",
-                "ext-curl": "*",
-                "ext-json": "*",
-                "ext-mbstring": "*",
-                "guzzlehttp/psr7": "~1.4",
-                "php": ">=5.6.0 <9.0",
-                "symfony/console": ">=2.7 <6.0",
-                "symfony/css-selector": ">=2.7 <6.0",
-                "symfony/event-dispatcher": ">=2.7 <6.0",
-                "symfony/finder": ">=2.7 <6.0",
-                "symfony/yaml": ">=2.7 <6.0"
+                "php": "^7.1|^8.0",
+                "psr/http-server-middleware": "^1.0"
             },
             "require-dev": {
-                "codeception/module-asserts": "1.*@dev",
-                "codeception/module-cli": "1.*@dev",
-                "codeception/module-db": "1.*@dev",
-                "codeception/module-filesystem": "1.*@dev",
-                "codeception/module-phpbrowser": "1.*@dev",
-                "codeception/specify": "~0.3",
-                "codeception/util-universalframework": "*@dev",
-                "monolog/monolog": "~1.8",
-                "squizlabs/php_codesniffer": "~2.0",
-                "symfony/process": ">=2.7 <6.0",
-                "vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0 | ^5.0"
-            },
-            "suggest": {
-                "codeception/specify": "BDD-style code blocks",
-                "codeception/verify": "BDD-style assertions",
-                "hoa/console": "For interactive console functionality",
-                "stecman/symfony-console-completion": "For BASH autocompletion",
-                "symfony/phpunit-bridge": "For phpunit-bridge support"
+                "overtrue/phplint": "^1.0",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "squizlabs/php_codesniffer": "^3.2",
+                "tuupola/http-factory": "^0.4.0|^1.0",
+                "zendframework/zend-diactoros": "^1.6.0|^2.0"
             },
-            "bin": [
-                "codecept"
-            ],
             "type": "library",
-            "extra": {
-                "branch-alias": []
-            },
             "autoload": {
                 "psr-4": {
-                    "Codeception\\": "src/Codeception",
-                    "Codeception\\Extension\\": "ext"
+                    "Tuupola\\Middleware\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -3262,56 +3435,66 @@
             ],
             "authors": [
                 {
-                    "name": "Michael Bodnarchuk",
-                    "email": "davert@mail.ua",
-                    "homepage": "http://codegyre.com"
+                    "name": "Mika Tuupola",
+                    "email": "tuupola@appelsiini.net",
+                    "homepage": "https://appelsiini.net/",
+                    "role": "Developer"
                 }
             ],
-            "description": "BDD-style testing framework",
-            "homepage": "http://codeception.com/",
+            "description": "Compatibility layer for PSR-7 double pass and PSR-15 middlewares.",
+            "homepage": "https://github.com/tuupola/callable-handler",
             "keywords": [
-                "BDD",
-                "TDD",
-                "acceptance testing",
-                "functional testing",
-                "unit testing"
+                "middleware",
+                "psr-15",
+                "psr-7"
             ],
             "support": {
-                "issues": "https://github.com/Codeception/Codeception/issues",
-                "source": "https://github.com/Codeception/Codeception/tree/4.1.21"
+                "issues": "https://github.com/tuupola/callable-handler/issues",
+                "source": "https://github.com/tuupola/callable-handler/tree/1.1.0"
             },
             "funding": [
                 {
-                    "url": "https://opencollective.com/codeception",
-                    "type": "open_collective"
+                    "url": "https://github.com/tuupola",
+                    "type": "github"
                 }
             ],
-            "time": "2021-05-28T17:43:39+00:00"
+            "time": "2020-09-09T08:31:54+00:00"
         },
         {
-            "name": "codeception/lib-asserts",
-            "version": "1.13.2",
+            "name": "tuupola/cors-middleware",
+            "version": "1.2.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Codeception/lib-asserts.git",
-                "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6"
+                "url": "https://github.com/tuupola/cors-middleware.git",
+                "reference": "4f085d11f349e83d18f1eb5802551353b2b093a3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/184231d5eab66bc69afd6b9429344d80c67a33b6",
-                "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6",
+                "url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/4f085d11f349e83d18f1eb5802551353b2b093a3",
+                "reference": "4f085d11f349e83d18f1eb5802551353b2b093a3",
                 "shasum": ""
             },
             "require": {
-                "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3 | ^9.0",
-                "ext-dom": "*",
-                "php": ">=5.6.0 <9.0"
+                "neomerx/cors-psr7": "^1.0.4",
+                "php": "^7.1|^8.0",
+                "psr/http-message": "^1.0.1",
+                "psr/http-server-middleware": "^1.0",
+                "tuupola/callable-handler": "^1.0",
+                "tuupola/http-factory": "^1.0.2"
+            },
+            "require-dev": {
+                "equip/dispatch": "^2.0",
+                "overtrue/phplint": "^1.0",
+                "phpstan/phpstan": "^0.12.42",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "squizlabs/php_codesniffer": "^3.5",
+                "zendframework/zend-diactoros": "^1.0|^2.0"
             },
             "type": "library",
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "Tuupola\\Middleware\\": "src"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3319,56 +3502,67 @@
             ],
             "authors": [
                 {
-                    "name": "Michael Bodnarchuk",
-                    "email": "davert@mail.ua",
-                    "homepage": "http://codegyre.com"
-                },
-                {
-                    "name": "Gintautas Miselis"
-                },
-                {
-                    "name": "Gustavo Nieves",
-                    "homepage": "https://medium.com/@ganieves"
+                    "name": "Mika Tuupola",
+                    "email": "tuupola@appelsiini.net",
+                    "homepage": "https://appelsiini.net/",
+                    "role": "Developer"
                 }
             ],
-            "description": "Assertion methods used by Codeception core and Asserts module",
-            "homepage": "https://codeception.com/",
+            "description": "PSR-7 and PSR-15 CORS middleware",
+            "homepage": "https://github.com/tuupola/cors-middleware",
             "keywords": [
-                "codeception"
+                "cors",
+                "middleware",
+                "psr-15",
+                "psr-7"
             ],
             "support": {
-                "issues": "https://github.com/Codeception/lib-asserts/issues",
-                "source": "https://github.com/Codeception/lib-asserts/tree/1.13.2"
+                "issues": "https://github.com/tuupola/cors-middleware/issues",
+                "source": "https://github.com/tuupola/cors-middleware/tree/1.2.1"
             },
-            "time": "2020-10-21T16:26:20+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/tuupola",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-29T11:01:06+00:00"
         },
         {
-            "name": "codeception/module-asserts",
-            "version": "1.3.1",
+            "name": "tuupola/http-factory",
+            "version": "1.3.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Codeception/module-asserts.git",
-                "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de"
+                "url": "https://github.com/tuupola/http-factory.git",
+                "reference": "aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/59374f2fef0cabb9e8ddb53277e85cdca74328de",
-                "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de",
+                "url": "https://api.github.com/repos/tuupola/http-factory/zipball/aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac",
+                "reference": "aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac",
                 "shasum": ""
             },
             "require": {
-                "codeception/codeception": "*@dev",
-                "codeception/lib-asserts": "^1.13.1",
-                "php": ">=5.6.0 <9.0"
+                "php": "^7.1|^8.0",
+                "psr/http-factory": "^1.0"
             },
             "conflict": {
-                "codeception/codeception": "<4.0"
+                "nyholm/psr7": "<1.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "^1.0"
+            },
+            "require-dev": {
+                "http-interop/http-factory-tests": "^0.7.0",
+                "overtrue/phplint": "^1.0",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "squizlabs/php_codesniffer": "^3.0"
             },
             "type": "library",
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "Tuupola\\Http\\Factory\\": "src"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3376,58 +3570,61 @@
             ],
             "authors": [
                 {
-                    "name": "Michael Bodnarchuk"
-                },
-                {
-                    "name": "Gintautas Miselis"
-                },
-                {
-                    "name": "Gustavo Nieves",
-                    "homepage": "https://medium.com/@ganieves"
+                    "name": "Mika Tuupola",
+                    "email": "tuupola@appelsiini.net",
+                    "homepage": "https://appelsiini.net/",
+                    "role": "Developer"
                 }
             ],
-            "description": "Codeception module containing various assertions",
-            "homepage": "https://codeception.com/",
+            "description": "Lightweight autodiscovering PSR-17 HTTP factories",
+            "homepage": "https://github.com/tuupola/http-factory",
             "keywords": [
-                "assertions",
-                "asserts",
-                "codeception"
+                "http",
+                "psr-17",
+                "psr-7"
             ],
             "support": {
-                "issues": "https://github.com/Codeception/module-asserts/issues",
-                "source": "https://github.com/Codeception/module-asserts/tree/1.3.1"
+                "issues": "https://github.com/tuupola/http-factory/issues",
+                "source": "https://github.com/tuupola/http-factory/tree/1.3.0"
             },
-            "time": "2020-10-21T16:48:15+00:00"
-        },
+            "funding": [
+                {
+                    "url": "https://github.com/tuupola",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-01T07:46:32+00:00"
+        }
+    ],
+    "packages-dev": [
         {
-            "name": "codeception/phpunit-wrapper",
-            "version": "8.1.4",
+            "name": "adlawson/vfs",
+            "version": "0.12.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Codeception/phpunit-wrapper.git",
-                "reference": "f41335f0b4dd17cf7bbc63e87943b3ae72a8bbc3"
+                "url": "https://github.com/adlawson/php-vfs.git",
+                "reference": "e955034419d6a8f92c9a8ea2e626eeed96b41095"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/f41335f0b4dd17cf7bbc63e87943b3ae72a8bbc3",
-                "reference": "f41335f0b4dd17cf7bbc63e87943b3ae72a8bbc3",
+                "url": "https://api.github.com/repos/adlawson/php-vfs/zipball/e955034419d6a8f92c9a8ea2e626eeed96b41095",
+                "reference": "e955034419d6a8f92c9a8ea2e626eeed96b41095",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2",
-                "phpunit/php-code-coverage": "^7.0",
-                "phpunit/phpunit": "^8.0",
-                "sebastian/comparator": "^3.0",
-                "sebastian/diff": "^3.0"
+                "php": ">=5.5",
+                "psr/log": "^1.0"
             },
             "require-dev": {
-                "codeception/specify": "*",
-                "vlucas/phpdotenv": "^3.0"
+                "adlawson/timezone": "^1.0",
+                "fabpot/php-cs-fixer": "^1.9",
+                "mockery/mockery": "^0.9",
+                "phpunit/phpunit": "^4.7"
             },
             "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Codeception\\PHPUnit\\": "src/"
+                    "Vfs\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -3436,81 +3633,115 @@
             ],
             "authors": [
                 {
-                    "name": "Davert",
-                    "email": "davert.php@resend.cc"
+                    "name": "Andrew Lawson",
+                    "homepage": "http://adlawson.com"
                 }
             ],
-            "description": "PHPUnit classes used by Codeception",
+            "description": "Virtual file system",
+            "homepage": "https://github.com/adlawson/php-vfs",
+            "keywords": [
+                "dir",
+                "directory",
+                "file",
+                "fs",
+                "read",
+                "stream",
+                "system",
+                "virtual",
+                "wrapper",
+                "write"
+            ],
             "support": {
-                "issues": "https://github.com/Codeception/phpunit-wrapper/issues",
-                "source": "https://github.com/Codeception/phpunit-wrapper/tree/8.1.4"
+                "issues": "https://github.com/adlawson/php-vfs/issues",
+                "source": "https://github.com/adlawson/php-vfs/tree/develop"
             },
-            "time": "2020-12-28T14:00:08+00:00"
+            "time": "2016-02-20T12:46:01+00:00"
         },
         {
-            "name": "codeception/stub",
-            "version": "3.7.0",
+            "name": "behat/gherkin",
+            "version": "v4.8.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Codeception/Stub.git",
-                "reference": "468dd5fe659f131fc997f5196aad87512f9b1304"
+                "url": "https://github.com/Behat/Gherkin.git",
+                "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Codeception/Stub/zipball/468dd5fe659f131fc997f5196aad87512f9b1304",
-                "reference": "468dd5fe659f131fc997f5196aad87512f9b1304",
+                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2391482cd003dfdc36b679b27e9f5326bd656acd",
+                "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd",
                 "shasum": ""
             },
             "require": {
-                "phpunit/phpunit": "^8.4 | ^9.0"
+                "php": "~7.2|~8.0"
+            },
+            "require-dev": {
+                "cucumber/cucumber": "dev-gherkin-16.0.0",
+                "phpunit/phpunit": "~8|~9",
+                "symfony/phpunit-bridge": "~3|~4|~5",
+                "symfony/yaml": "~3|~4|~5"
+            },
+            "suggest": {
+                "symfony/yaml": "If you want to parse features, represented in YAML files"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.4-dev"
+                }
+            },
             "autoload": {
-                "psr-4": {
-                    "Codeception\\": "src/"
+                "psr-0": {
+                    "Behat\\Gherkin": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "description": "Flexible Stub wrapper for PHPUnit's Mock Builder",
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                }
+            ],
+            "description": "Gherkin DSL parser for PHP",
+            "homepage": "http://behat.org/",
+            "keywords": [
+                "BDD",
+                "Behat",
+                "Cucumber",
+                "DSL",
+                "gherkin",
+                "parser"
+            ],
             "support": {
-                "issues": "https://github.com/Codeception/Stub/issues",
-                "source": "https://github.com/Codeception/Stub/tree/3.7.0"
+                "issues": "https://github.com/Behat/Gherkin/issues",
+                "source": "https://github.com/Behat/Gherkin/tree/v4.8.0"
             },
-            "time": "2020-07-03T15:54:43+00:00"
+            "time": "2021-02-04T12:44:21+00:00"
         },
         {
-            "name": "doctrine/instantiator",
-            "version": "1.4.0",
+            "name": "camspiers/json-pretty",
+            "version": "1.0.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/doctrine/instantiator.git",
-                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+                "url": "https://github.com/camspiers/json-pretty.git",
+                "reference": "17be37cb83af8014658da48fa0012604179039a7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
-                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+                "url": "https://api.github.com/repos/camspiers/json-pretty/zipball/17be37cb83af8014658da48fa0012604179039a7",
+                "reference": "17be37cb83af8014658da48fa0012604179039a7",
                 "shasum": ""
             },
-            "require": {
-                "php": "^7.1 || ^8.0"
-            },
             "require-dev": {
-                "doctrine/coding-standard": "^8.0",
-                "ext-pdo": "*",
-                "ext-phar": "*",
-                "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
-                "phpstan/phpstan": "^0.12",
-                "phpstan/phpstan-phpunit": "^0.12",
-                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+                "phpunit/phpunit": "~4.0"
             },
             "type": "library",
             "autoload": {
-                "psr-4": {
-                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                "psr-0": {
+                    "Camspiers": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -3519,94 +3750,45 @@
             ],
             "authors": [
                 {
-                    "name": "Marco Pivetta",
-                    "email": "ocramius@gmail.com",
-                    "homepage": "https://ocramius.github.io/"
+                    "name": "Cam Spiers",
+                    "email": "cameron@heyday.co.nz"
                 }
             ],
-            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
-            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
-            "keywords": [
-                "constructor",
-                "instantiate"
-            ],
+            "description": "Provides support for json pretty printing",
             "support": {
-                "issues": "https://github.com/doctrine/instantiator/issues",
-                "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
+                "issues": "https://github.com/camspiers/json-pretty/issues",
+                "source": "https://github.com/camspiers/json-pretty/tree/master"
             },
-            "funding": [
-                {
-                    "url": "https://www.doctrine-project.org/sponsorship.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://www.patreon.com/phpdoctrine",
-                    "type": "patreon"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2020-11-10T18:47:58+00:00"
+            "time": "2016-02-06T01:25:58+00:00"
         },
         {
-            "name": "monolog/monolog",
-            "version": "1.21.0",
+            "name": "clue/stream-filter",
+            "version": "v1.4.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952"
+                "url": "https://github.com/clue/stream-filter.git",
+                "reference": "5a58cc30a8bd6a4eb8f856adf61dd3e013f53f71"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952",
-                "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952",
+                "url": "https://api.github.com/repos/clue/stream-filter/zipball/5a58cc30a8bd6a4eb8f856adf61dd3e013f53f71",
+                "reference": "5a58cc30a8bd6a4eb8f856adf61dd3e013f53f71",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0",
-                "psr/log": "~1.0"
-            },
-            "provide": {
-                "psr/log-implementation": "1.0.0"
-            },
-            "require-dev": {
-                "aws/aws-sdk-php": "^2.4.9",
-                "doctrine/couchdb": "~1.0@dev",
-                "graylog2/gelf-php": "~1.0",
-                "jakub-onderka/php-parallel-lint": "0.9",
-                "php-amqplib/php-amqplib": "~2.4",
-                "php-console/php-console": "^3.1.3",
-                "phpunit/phpunit": "~4.5",
-                "phpunit/phpunit-mock-objects": "2.3.0",
-                "ruflin/elastica": ">=0.90 <3.0",
-                "sentry/sentry": "^0.13",
-                "swiftmailer/swiftmailer": "~5.3"
-            },
-            "suggest": {
-                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
-                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
-                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-                "ext-mongo": "Allow sending log messages to a MongoDB server",
-                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
-                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
-                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
-                "php-console/php-console": "Allow sending log messages to Google Chrome",
-                "rollbar/rollbar": "Allow sending log messages to Rollbar",
-                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
-                "sentry/sentry": "Allow sending log messages to a Sentry server"
+                "php": ">=5.3"
             },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0.x-dev"
-                }
+            "require-dev": {
+                "phpunit/phpunit": "^5.0 || ^4.8"
             },
+            "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Monolog\\": "src/Monolog"
-                }
+                    "Clue\\StreamFilter\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -3614,109 +3796,152 @@
             ],
             "authors": [
                 {
-                    "name": "Jordi Boggiano",
-                    "email": "j.boggiano@seld.be",
-                    "homepage": "http://seld.be"
+                    "name": "Christian Lück",
+                    "email": "christian@lueck.tv"
                 }
             ],
-            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
-            "homepage": "http://github.com/Seldaek/monolog",
+            "description": "A simple and modern approach to stream filtering in PHP",
+            "homepage": "https://github.com/clue/php-stream-filter",
             "keywords": [
-                "log",
-                "logging",
-                "psr-3"
+                "bucket brigade",
+                "callback",
+                "filter",
+                "php_user_filter",
+                "stream",
+                "stream_filter_append",
+                "stream_filter_register"
             ],
             "support": {
-                "issues": "https://github.com/Seldaek/monolog/issues",
-                "source": "https://github.com/Seldaek/monolog/tree/1.x"
+                "issues": "https://github.com/clue/stream-filter/issues",
+                "source": "https://github.com/clue/stream-filter/tree/v1.4.1"
             },
-            "time": "2016-07-29T03:23:52+00:00"
+            "funding": [
+                {
+                    "url": "https://clue.engineering/support",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/clue",
+                    "type": "github"
+                }
+            ],
+            "time": "2019-04-09T12:31:48+00:00"
         },
         {
-            "name": "myclabs/deep-copy",
-            "version": "1.10.2",
+            "name": "codeception/codeception",
+            "version": "4.1.21",
             "source": {
                 "type": "git",
-                "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+                "url": "https://github.com/Codeception/Codeception.git",
+                "reference": "c25f20d842a7e3fa0a8e6abf0828f102c914d419"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
-                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+                "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c25f20d842a7e3fa0a8e6abf0828f102c914d419",
+                "reference": "c25f20d842a7e3fa0a8e6abf0828f102c914d419",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1 || ^8.0"
-            },
-            "replace": {
-                "myclabs/deep-copy": "self.version"
+                "behat/gherkin": "^4.4.0",
+                "codeception/lib-asserts": "^1.0",
+                "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0",
+                "codeception/stub": "^2.0 | ^3.0",
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "guzzlehttp/psr7": "~1.4",
+                "php": ">=5.6.0 <9.0",
+                "symfony/console": ">=2.7 <6.0",
+                "symfony/css-selector": ">=2.7 <6.0",
+                "symfony/event-dispatcher": ">=2.7 <6.0",
+                "symfony/finder": ">=2.7 <6.0",
+                "symfony/yaml": ">=2.7 <6.0"
             },
             "require-dev": {
-                "doctrine/collections": "^1.0",
-                "doctrine/common": "^2.6",
-                "phpunit/phpunit": "^7.1"
+                "codeception/module-asserts": "1.*@dev",
+                "codeception/module-cli": "1.*@dev",
+                "codeception/module-db": "1.*@dev",
+                "codeception/module-filesystem": "1.*@dev",
+                "codeception/module-phpbrowser": "1.*@dev",
+                "codeception/specify": "~0.3",
+                "codeception/util-universalframework": "*@dev",
+                "monolog/monolog": "~1.8",
+                "squizlabs/php_codesniffer": "~2.0",
+                "symfony/process": ">=2.7 <6.0",
+                "vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0 | ^5.0"
+            },
+            "suggest": {
+                "codeception/specify": "BDD-style code blocks",
+                "codeception/verify": "BDD-style assertions",
+                "hoa/console": "For interactive console functionality",
+                "stecman/symfony-console-completion": "For BASH autocompletion",
+                "symfony/phpunit-bridge": "For phpunit-bridge support"
             },
+            "bin": [
+                "codecept"
+            ],
             "type": "library",
+            "extra": {
+                "branch-alias": []
+            },
             "autoload": {
                 "psr-4": {
-                    "DeepCopy\\": "src/DeepCopy/"
-                },
-                "files": [
-                    "src/DeepCopy/deep_copy.php"
-                ]
+                    "Codeception\\": "src/Codeception",
+                    "Codeception\\Extension\\": "ext"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "description": "Create deep copies (clones) of your objects",
+            "authors": [
+                {
+                    "name": "Michael Bodnarchuk",
+                    "email": "davert@mail.ua",
+                    "homepage": "http://codegyre.com"
+                }
+            ],
+            "description": "BDD-style testing framework",
+            "homepage": "http://codeception.com/",
             "keywords": [
-                "clone",
-                "copy",
-                "duplicate",
-                "object",
-                "object graph"
+                "BDD",
+                "TDD",
+                "acceptance testing",
+                "functional testing",
+                "unit testing"
             ],
             "support": {
-                "issues": "https://github.com/myclabs/DeepCopy/issues",
-                "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
+                "issues": "https://github.com/Codeception/Codeception/issues",
+                "source": "https://github.com/Codeception/Codeception/tree/4.1.21"
             },
             "funding": [
                 {
-                    "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
-                    "type": "tidelift"
+                    "url": "https://opencollective.com/codeception",
+                    "type": "open_collective"
                 }
             ],
-            "time": "2020-11-13T09:40:50+00:00"
+            "time": "2021-05-28T17:43:39+00:00"
         },
         {
-            "name": "phar-io/manifest",
-            "version": "2.0.1",
+            "name": "codeception/lib-asserts",
+            "version": "1.13.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phar-io/manifest.git",
-                "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133"
+                "url": "https://github.com/Codeception/lib-asserts.git",
+                "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
-                "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
+                "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/184231d5eab66bc69afd6b9429344d80c67a33b6",
+                "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6",
                 "shasum": ""
             },
             "require": {
+                "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3 | ^9.0",
                 "ext-dom": "*",
-                "ext-phar": "*",
-                "ext-xmlwriter": "*",
-                "phar-io/version": "^3.0.1",
-                "php": "^7.2 || ^8.0"
+                "php": ">=5.6.0 <9.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.0.x-dev"
-                }
-            },
             "autoload": {
                 "classmap": [
                     "src/"
@@ -3724,48 +3949,54 @@
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
+                    "name": "Michael Bodnarchuk",
+                    "email": "davert@mail.ua",
+                    "homepage": "http://codegyre.com"
                 },
                 {
-                    "name": "Sebastian Heuer",
-                    "email": "sebastian@phpeople.de",
-                    "role": "Developer"
+                    "name": "Gintautas Miselis"
                 },
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "Developer"
+                    "name": "Gustavo Nieves",
+                    "homepage": "https://medium.com/@ganieves"
                 }
             ],
-            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+            "description": "Assertion methods used by Codeception core and Asserts module",
+            "homepage": "https://codeception.com/",
+            "keywords": [
+                "codeception"
+            ],
             "support": {
-                "issues": "https://github.com/phar-io/manifest/issues",
-                "source": "https://github.com/phar-io/manifest/tree/master"
+                "issues": "https://github.com/Codeception/lib-asserts/issues",
+                "source": "https://github.com/Codeception/lib-asserts/tree/1.13.2"
             },
-            "time": "2020-06-27T14:33:11+00:00"
+            "time": "2020-10-21T16:26:20+00:00"
         },
         {
-            "name": "phar-io/version",
-            "version": "3.1.0",
+            "name": "codeception/module-asserts",
+            "version": "1.3.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phar-io/version.git",
-                "reference": "bae7c545bef187884426f042434e561ab1ddb182"
+                "url": "https://github.com/Codeception/module-asserts.git",
+                "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182",
-                "reference": "bae7c545bef187884426f042434e561ab1ddb182",
+                "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/59374f2fef0cabb9e8ddb53277e85cdca74328de",
+                "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.2 || ^8.0"
+                "codeception/codeception": "*@dev",
+                "codeception/lib-asserts": "^1.13.1",
+                "php": ">=5.6.0 <9.0"
+            },
+            "conflict": {
+                "codeception/codeception": "<4.0"
             },
             "type": "library",
             "autoload": {
@@ -3775,68 +4006,62 @@
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
+                    "name": "Michael Bodnarchuk"
                 },
                 {
-                    "name": "Sebastian Heuer",
-                    "email": "sebastian@phpeople.de",
-                    "role": "Developer"
+                    "name": "Gintautas Miselis"
                 },
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "Developer"
+                    "name": "Gustavo Nieves",
+                    "homepage": "https://medium.com/@ganieves"
                 }
             ],
-            "description": "Library for handling version information and constraints",
+            "description": "Codeception module containing various assertions",
+            "homepage": "https://codeception.com/",
+            "keywords": [
+                "assertions",
+                "asserts",
+                "codeception"
+            ],
             "support": {
-                "issues": "https://github.com/phar-io/version/issues",
-                "source": "https://github.com/phar-io/version/tree/3.1.0"
+                "issues": "https://github.com/Codeception/module-asserts/issues",
+                "source": "https://github.com/Codeception/module-asserts/tree/1.3.1"
             },
-            "time": "2021-02-23T14:00:09+00:00"
+            "time": "2020-10-21T16:48:15+00:00"
         },
         {
-            "name": "php-http/curl-client",
-            "version": "v1.7.1",
+            "name": "codeception/phpunit-wrapper",
+            "version": "8.1.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-http/curl-client.git",
-                "reference": "6341a93d00e5d953fc868a3928b5167e6513f2b6"
+                "url": "https://github.com/Codeception/phpunit-wrapper.git",
+                "reference": "f41335f0b4dd17cf7bbc63e87943b3ae72a8bbc3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-http/curl-client/zipball/6341a93d00e5d953fc868a3928b5167e6513f2b6",
-                "reference": "6341a93d00e5d953fc868a3928b5167e6513f2b6",
+                "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/f41335f0b4dd17cf7bbc63e87943b3ae72a8bbc3",
+                "reference": "f41335f0b4dd17cf7bbc63e87943b3ae72a8bbc3",
                 "shasum": ""
             },
             "require": {
-                "ext-curl": "*",
-                "php": "^5.5 || ^7.0",
-                "php-http/discovery": "^1.0",
-                "php-http/httplug": "^1.0",
-                "php-http/message": "^1.2",
-                "php-http/message-factory": "^1.0.2"
-            },
-            "provide": {
-                "php-http/async-client-implementation": "1.0",
-                "php-http/client-implementation": "1.0"
+                "php": ">=7.2",
+                "phpunit/php-code-coverage": "^7.0",
+                "phpunit/phpunit": "^8.0",
+                "sebastian/comparator": "^3.0",
+                "sebastian/diff": "^3.0"
             },
             "require-dev": {
-                "guzzlehttp/psr7": "^1.0",
-                "php-http/client-integration-tests": "^0.6",
-                "phpunit/phpunit": "^4.8.27",
-                "zendframework/zend-diactoros": "^1.0"
+                "codeception/specify": "*",
+                "vlucas/phpdotenv": "^3.0"
             },
             "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Http\\Client\\Curl\\": "src/"
+                    "Codeception\\PHPUnit\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -3845,122 +4070,81 @@
             ],
             "authors": [
                 {
-                    "name": "Михаил Красильников",
-                    "email": "m.krasilnikov@yandex.ru"
+                    "name": "Davert",
+                    "email": "davert.php@resend.cc"
                 }
             ],
-            "description": "cURL client for PHP-HTTP",
-            "homepage": "http://php-http.org",
-            "keywords": [
-                "curl",
-                "http"
-            ],
+            "description": "PHPUnit classes used by Codeception",
             "support": {
-                "issues": "https://github.com/php-http/curl-client/issues",
-                "source": "https://github.com/php-http/curl-client/tree/v1.7.1"
+                "issues": "https://github.com/Codeception/phpunit-wrapper/issues",
+                "source": "https://github.com/Codeception/phpunit-wrapper/tree/8.1.4"
             },
-            "time": "2018-03-26T19:21:48+00:00"
+            "time": "2020-12-28T14:00:08+00:00"
         },
         {
-            "name": "php-http/discovery",
-            "version": "1.6.1",
+            "name": "codeception/stub",
+            "version": "3.7.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-http/discovery.git",
-                "reference": "684855f2c2e9d0a61868b8f8d6bd0295c8a4b651"
+                "url": "https://github.com/Codeception/Stub.git",
+                "reference": "468dd5fe659f131fc997f5196aad87512f9b1304"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-http/discovery/zipball/684855f2c2e9d0a61868b8f8d6bd0295c8a4b651",
-                "reference": "684855f2c2e9d0a61868b8f8d6bd0295c8a4b651",
+                "url": "https://api.github.com/repos/Codeception/Stub/zipball/468dd5fe659f131fc997f5196aad87512f9b1304",
+                "reference": "468dd5fe659f131fc997f5196aad87512f9b1304",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5 || ^7.0"
-            },
-            "conflict": {
-                "nyholm/psr7": "<1.0"
-            },
-            "require-dev": {
-                "php-http/httplug": "^1.0 || ^2.0",
-                "php-http/message-factory": "^1.0",
-                "phpspec/phpspec": "^2.4",
-                "puli/composer-plugin": "1.0.0-beta10"
-            },
-            "suggest": {
-                "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories",
-                "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details."
+                "phpunit/phpunit": "^8.4 | ^9.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.5-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
-                    "Http\\Discovery\\": "src/"
+                    "Codeception\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "authors": [
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com"
-                }
-            ],
-            "description": "Finds installed HTTPlug implementations and PSR-7 message factories",
-            "homepage": "http://php-http.org",
-            "keywords": [
-                "adapter",
-                "client",
-                "discovery",
-                "factory",
-                "http",
-                "message",
-                "psr7"
-            ],
+            "description": "Flexible Stub wrapper for PHPUnit's Mock Builder",
             "support": {
-                "issues": "https://github.com/php-http/discovery/issues",
-                "source": "https://github.com/php-http/discovery/tree/master"
+                "issues": "https://github.com/Codeception/Stub/issues",
+                "source": "https://github.com/Codeception/Stub/tree/3.7.0"
             },
-            "time": "2019-02-23T07:42:53+00:00"
+            "time": "2020-07-03T15:54:43+00:00"
         },
         {
-            "name": "php-http/httplug",
-            "version": "v1.1.0",
+            "name": "doctrine/instantiator",
+            "version": "1.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-http/httplug.git",
-                "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018"
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018",
-                "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.4",
-                "php-http/promise": "^1.0",
-                "psr/http-message": "^1.0"
+                "php": "^7.1 || ^8.0"
             },
             "require-dev": {
-                "henrikbjorn/phpspec-code-coverage": "^1.0",
-                "phpspec/phpspec": "^2.4"
+                "doctrine/coding-standard": "^8.0",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+                "phpstan/phpstan": "^0.12",
+                "phpstan/phpstan-phpunit": "^0.12",
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
-                    "Http\\Client\\": "src/"
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -3969,78 +4153,94 @@
             ],
             "authors": [
                 {
-                    "name": "Eric GELOEN",
-                    "email": "geloen.eric@gmail.com"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com"
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "https://ocramius.github.io/"
                 }
             ],
-            "description": "HTTPlug, the HTTP client abstraction for PHP",
-            "homepage": "http://httplug.io",
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
             "keywords": [
-                "client",
-                "http"
+                "constructor",
+                "instantiate"
             ],
             "support": {
-                "issues": "https://github.com/php-http/httplug/issues",
-                "source": "https://github.com/php-http/httplug/tree/master"
+                "issues": "https://github.com/doctrine/instantiator/issues",
+                "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
             },
-            "time": "2016-08-31T08:30:17+00:00"
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-11-10T18:47:58+00:00"
         },
         {
-            "name": "php-http/message",
-            "version": "1.7.2",
+            "name": "monolog/monolog",
+            "version": "1.21.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-http/message.git",
-                "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1"
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-http/message/zipball/b159ffe570dffd335e22ef0b91a946eacb182fa1",
-                "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952",
+                "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952",
                 "shasum": ""
             },
             "require": {
-                "clue/stream-filter": "^1.4",
-                "php": "^5.4 || ^7.0",
-                "php-http/message-factory": "^1.0.2",
-                "psr/http-message": "^1.0"
+                "php": ">=5.3.0",
+                "psr/log": "~1.0"
             },
             "provide": {
-                "php-http/message-factory-implementation": "1.0"
+                "psr/log-implementation": "1.0.0"
             },
             "require-dev": {
-                "akeneo/phpspec-skip-example-extension": "^1.0",
-                "coduo/phpspec-data-provider-extension": "^1.0",
-                "ext-zlib": "*",
-                "guzzlehttp/psr7": "^1.0",
-                "henrikbjorn/phpspec-code-coverage": "^1.0",
-                "phpspec/phpspec": "^2.4",
-                "slim/slim": "^3.0",
-                "zendframework/zend-diactoros": "^1.0"
+                "aws/aws-sdk-php": "^2.4.9",
+                "doctrine/couchdb": "~1.0@dev",
+                "graylog2/gelf-php": "~1.0",
+                "jakub-onderka/php-parallel-lint": "0.9",
+                "php-amqplib/php-amqplib": "~2.4",
+                "php-console/php-console": "^3.1.3",
+                "phpunit/phpunit": "~4.5",
+                "phpunit/phpunit-mock-objects": "2.3.0",
+                "ruflin/elastica": ">=0.90 <3.0",
+                "sentry/sentry": "^0.13",
+                "swiftmailer/swiftmailer": "~5.3"
             },
             "suggest": {
-                "ext-zlib": "Used with compressor/decompressor streams",
-                "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories",
-                "slim/slim": "Used with Slim Framework PSR-7 implementation",
-                "zendframework/zend-diactoros": "Used with Diactoros Factories"
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "php-console/php-console": "Allow sending log messages to Google Chrome",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+                "sentry/sentry": "Allow sending log messages to a Sentry server"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.6-dev"
+                    "dev-master": "2.0.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Http\\Message\\": "src/"
-                },
-                "files": [
-                    "src/filters.php"
-                ]
+                    "Monolog\\": "src/Monolog"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -4048,217 +4248,229 @@
             ],
             "authors": [
                 {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com"
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
                 }
             ],
-            "description": "HTTP Message related tools",
-            "homepage": "http://php-http.org",
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "http://github.com/Seldaek/monolog",
             "keywords": [
-                "http",
-                "message",
-                "psr-7"
+                "log",
+                "logging",
+                "psr-3"
             ],
             "support": {
-                "issues": "https://github.com/php-http/message/issues",
-                "source": "https://github.com/php-http/message/tree/master"
+                "issues": "https://github.com/Seldaek/monolog/issues",
+                "source": "https://github.com/Seldaek/monolog/tree/1.x"
             },
-            "time": "2018-11-01T09:32:41+00:00"
+            "time": "2016-07-29T03:23:52+00:00"
         },
-        {
-            "name": "php-http/message-factory",
-            "version": "v1.0.2",
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.10.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-http/message-factory.git",
-                "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
-                "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.4",
-                "psr/http-message": "^1.0"
+                "php": "^7.1 || ^8.0"
             },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
+            "replace": {
+                "myclabs/deep-copy": "self.version"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.0",
+                "doctrine/common": "^2.6",
+                "phpunit/phpunit": "^7.1"
             },
+            "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Http\\Message\\": "src/"
-                }
+                    "DeepCopy\\": "src/DeepCopy/"
+                },
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
-            "authors": [
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com"
-                }
-            ],
-            "description": "Factory interfaces for PSR-7 HTTP Message",
-            "homepage": "http://php-http.org",
+            "description": "Create deep copies (clones) of your objects",
             "keywords": [
-                "factory",
-                "http",
-                "message",
-                "stream",
-                "uri"
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
             ],
             "support": {
-                "issues": "https://github.com/php-http/message-factory/issues",
-                "source": "https://github.com/php-http/message-factory/tree/master"
+                "issues": "https://github.com/myclabs/DeepCopy/issues",
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
             },
-            "time": "2015-12-19T14:08:53+00:00"
+            "funding": [
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-11-13T09:40:50+00:00"
         },
         {
-            "name": "php-http/promise",
-            "version": "v1.0.0",
+            "name": "phar-io/manifest",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-http/promise.git",
-                "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980"
+                "url": "https://github.com/phar-io/manifest.git",
+                "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980",
-                "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
+                "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
                 "shasum": ""
             },
-            "require-dev": {
-                "henrikbjorn/phpspec-code-coverage": "^1.0",
-                "phpspec/phpspec": "^2.4"
+            "require": {
+                "ext-dom": "*",
+                "ext-phar": "*",
+                "ext-xmlwriter": "*",
+                "phar-io/version": "^3.0.1",
+                "php": "^7.2 || ^8.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1-dev"
+                    "dev-master": "2.0.x-dev"
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Http\\Promise\\": "src/"
-                }
+                "classmap": [
+                    "src/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com"
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
                 },
                 {
-                    "name": "Joel Wurtz",
-                    "email": "joel.wurtz@gmail.com"
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
                 }
             ],
-            "description": "Promise used for asynchronous HTTP requests",
-            "homepage": "http://httplug.io",
-            "keywords": [
-                "promise"
-            ],
+            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
             "support": {
-                "issues": "https://github.com/php-http/promise/issues",
-                "source": "https://github.com/php-http/promise/tree/master"
+                "issues": "https://github.com/phar-io/manifest/issues",
+                "source": "https://github.com/phar-io/manifest/tree/master"
             },
-            "time": "2016-01-26T13:27:02+00:00"
+            "time": "2020-06-27T14:33:11+00:00"
         },
         {
-            "name": "phpdocumentor/reflection-common",
-            "version": "2.2.0",
+            "name": "phar-io/version",
+            "version": "3.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
-                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+                "url": "https://github.com/phar-io/version.git",
+                "reference": "bae7c545bef187884426f042434e561ab1ddb182"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
-                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182",
+                "reference": "bae7c545bef187884426f042434e561ab1ddb182",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.2 || ^8.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-2.x": "2.x-dev"
-                }
-            },
             "autoload": {
-                "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src/"
-                }
+                "classmap": [
+                    "src/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Jaap van Otterdijk",
-                    "email": "opensource@ijaap.nl"
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
                 }
             ],
-            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
-            "homepage": "http://www.phpdoc.org",
-            "keywords": [
-                "FQSEN",
-                "phpDocumentor",
-                "phpdoc",
-                "reflection",
-                "static analysis"
-            ],
+            "description": "Library for handling version information and constraints",
             "support": {
-                "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+                "issues": "https://github.com/phar-io/version/issues",
+                "source": "https://github.com/phar-io/version/tree/3.1.0"
             },
-            "time": "2020-06-27T09:03:43+00:00"
+            "time": "2021-02-23T14:00:09+00:00"
         },
         {
-            "name": "phpdocumentor/reflection-docblock",
-            "version": "5.2.2",
+            "name": "php-http/curl-client",
+            "version": "v1.7.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+                "url": "https://github.com/php-http/curl-client.git",
+                "reference": "6341a93d00e5d953fc868a3928b5167e6513f2b6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
-                "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+                "url": "https://api.github.com/repos/php-http/curl-client/zipball/6341a93d00e5d953fc868a3928b5167e6513f2b6",
+                "reference": "6341a93d00e5d953fc868a3928b5167e6513f2b6",
                 "shasum": ""
             },
             "require": {
-                "ext-filter": "*",
-                "php": "^7.2 || ^8.0",
-                "phpdocumentor/reflection-common": "^2.2",
-                "phpdocumentor/type-resolver": "^1.3",
-                "webmozart/assert": "^1.9.1"
+                "ext-curl": "*",
+                "php": "^5.5 || ^7.0",
+                "php-http/discovery": "^1.0",
+                "php-http/httplug": "^1.0",
+                "php-http/message": "^1.2",
+                "php-http/message-factory": "^1.0.2"
+            },
+            "provide": {
+                "php-http/async-client-implementation": "1.0",
+                "php-http/client-implementation": "1.0"
             },
             "require-dev": {
-                "mockery/mockery": "~1.3.2"
+                "guzzlehttp/psr7": "^1.0",
+                "php-http/client-integration-tests": "^0.6",
+                "phpunit/phpunit": "^4.8.27",
+                "zendframework/zend-diactoros": "^1.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "5.x-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src"
+                    "Http\\Client\\Curl\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -4267,51 +4479,61 @@
             ],
             "authors": [
                 {
-                    "name": "Mike van Riel",
-                    "email": "me@mikevanriel.com"
-                },
-                {
-                    "name": "Jaap van Otterdijk",
-                    "email": "account@ijaap.nl"
+                    "name": "Михаил Красильников",
+                    "email": "m.krasilnikov@yandex.ru"
                 }
             ],
-            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+            "description": "cURL client for PHP-HTTP",
+            "homepage": "http://php-http.org",
+            "keywords": [
+                "curl",
+                "http"
+            ],
             "support": {
-                "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
+                "issues": "https://github.com/php-http/curl-client/issues",
+                "source": "https://github.com/php-http/curl-client/tree/v1.7.1"
             },
-            "time": "2020-09-03T19:13:55+00:00"
+            "time": "2018-03-26T19:21:48+00:00"
         },
         {
-            "name": "phpdocumentor/type-resolver",
-            "version": "1.4.0",
+            "name": "php-http/discovery",
+            "version": "1.6.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+                "url": "https://github.com/php-http/discovery.git",
+                "reference": "684855f2c2e9d0a61868b8f8d6bd0295c8a4b651"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
-                "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+                "url": "https://api.github.com/repos/php-http/discovery/zipball/684855f2c2e9d0a61868b8f8d6bd0295c8a4b651",
+                "reference": "684855f2c2e9d0a61868b8f8d6bd0295c8a4b651",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.2 || ^8.0",
-                "phpdocumentor/reflection-common": "^2.0"
+                "php": "^5.5 || ^7.0"
+            },
+            "conflict": {
+                "nyholm/psr7": "<1.0"
             },
             "require-dev": {
-                "ext-tokenizer": "*"
+                "php-http/httplug": "^1.0 || ^2.0",
+                "php-http/message-factory": "^1.0",
+                "phpspec/phpspec": "^2.4",
+                "puli/composer-plugin": "1.0.0-beta10"
+            },
+            "suggest": {
+                "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories",
+                "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details."
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-1.x": "1.x-dev"
+                    "dev-master": "1.5-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": "src"
+                    "Http\\Discovery\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -4320,51 +4542,59 @@
             ],
             "authors": [
                 {
-                    "name": "Mike van Riel",
-                    "email": "me@mikevanriel.com"
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
                 }
             ],
-            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+            "description": "Finds installed HTTPlug implementations and PSR-7 message factories",
+            "homepage": "http://php-http.org",
+            "keywords": [
+                "adapter",
+                "client",
+                "discovery",
+                "factory",
+                "http",
+                "message",
+                "psr7"
+            ],
             "support": {
-                "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
-                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
+                "issues": "https://github.com/php-http/discovery/issues",
+                "source": "https://github.com/php-http/discovery/tree/master"
             },
-            "time": "2020-09-17T18:55:26+00:00"
+            "time": "2019-02-23T07:42:53+00:00"
         },
         {
-            "name": "phpspec/prophecy",
-            "version": "1.13.0",
+            "name": "php-http/httplug",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
+                "url": "https://github.com/php-http/httplug.git",
+                "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
-                "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
+                "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018",
+                "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018",
                 "shasum": ""
             },
             "require": {
-                "doctrine/instantiator": "^1.2",
-                "php": "^7.2 || ~8.0, <8.1",
-                "phpdocumentor/reflection-docblock": "^5.2",
-                "sebastian/comparator": "^3.0 || ^4.0",
-                "sebastian/recursion-context": "^3.0 || ^4.0"
+                "php": ">=5.4",
+                "php-http/promise": "^1.0",
+                "psr/http-message": "^1.0"
             },
             "require-dev": {
-                "phpspec/phpspec": "^6.0",
-                "phpunit/phpunit": "^8.0 || ^9.0"
+                "henrikbjorn/phpspec-code-coverage": "^1.0",
+                "phpspec/phpspec": "^2.4"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.11.x-dev"
+                    "dev-master": "1.1-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Prophecy\\": "src/Prophecy"
+                    "Http\\Client\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -4373,451 +4603,402 @@
             ],
             "authors": [
                 {
-                    "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
+                    "name": "Eric GELOEN",
+                    "email": "geloen.eric@gmail.com"
                 },
                 {
-                    "name": "Marcello Duarte",
-                    "email": "marcello.duarte@gmail.com"
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
                 }
             ],
-            "description": "Highly opinionated mocking framework for PHP 5.3+",
-            "homepage": "https://github.com/phpspec/prophecy",
+            "description": "HTTPlug, the HTTP client abstraction for PHP",
+            "homepage": "http://httplug.io",
             "keywords": [
-                "Double",
-                "Dummy",
-                "fake",
-                "mock",
-                "spy",
-                "stub"
+                "client",
+                "http"
             ],
             "support": {
-                "issues": "https://github.com/phpspec/prophecy/issues",
-                "source": "https://github.com/phpspec/prophecy/tree/1.13.0"
+                "issues": "https://github.com/php-http/httplug/issues",
+                "source": "https://github.com/php-http/httplug/tree/master"
             },
-            "time": "2021-03-17T13:42:18+00:00"
+            "time": "2016-08-31T08:30:17+00:00"
         },
         {
-            "name": "phpunit/php-code-coverage",
-            "version": "7.0.14",
+            "name": "php-http/message",
+            "version": "1.7.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c"
+                "url": "https://github.com/php-http/message.git",
+                "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c",
-                "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c",
+                "url": "https://api.github.com/repos/php-http/message/zipball/b159ffe570dffd335e22ef0b91a946eacb182fa1",
+                "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1",
                 "shasum": ""
             },
             "require": {
-                "ext-dom": "*",
-                "ext-xmlwriter": "*",
-                "php": ">=7.2",
-                "phpunit/php-file-iterator": "^2.0.2",
-                "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-token-stream": "^3.1.1 || ^4.0",
-                "sebastian/code-unit-reverse-lookup": "^1.0.1",
-                "sebastian/environment": "^4.2.2",
-                "sebastian/version": "^2.0.1",
-                "theseer/tokenizer": "^1.1.3"
+                "clue/stream-filter": "^1.4",
+                "php": "^5.4 || ^7.0",
+                "php-http/message-factory": "^1.0.2",
+                "psr/http-message": "^1.0"
+            },
+            "provide": {
+                "php-http/message-factory-implementation": "1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.2.2"
+                "akeneo/phpspec-skip-example-extension": "^1.0",
+                "coduo/phpspec-data-provider-extension": "^1.0",
+                "ext-zlib": "*",
+                "guzzlehttp/psr7": "^1.0",
+                "henrikbjorn/phpspec-code-coverage": "^1.0",
+                "phpspec/phpspec": "^2.4",
+                "slim/slim": "^3.0",
+                "zendframework/zend-diactoros": "^1.0"
             },
             "suggest": {
-                "ext-xdebug": "^2.7.2"
+                "ext-zlib": "Used with compressor/decompressor streams",
+                "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories",
+                "slim/slim": "Used with Slim Framework PSR-7 implementation",
+                "zendframework/zend-diactoros": "Used with Diactoros Factories"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "7.0-dev"
+                    "dev-master": "1.6-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "src/"
+                "psr-4": {
+                    "Http\\Message\\": "src/"
+                },
+                "files": [
+                    "src/filters.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
                 }
             ],
-            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
-            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "description": "HTTP Message related tools",
+            "homepage": "http://php-http.org",
             "keywords": [
-                "coverage",
-                "testing",
-                "xunit"
+                "http",
+                "message",
+                "psr-7"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14"
+                "issues": "https://github.com/php-http/message/issues",
+                "source": "https://github.com/php-http/message/tree/master"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-12-02T13:39:03+00:00"
+            "time": "2018-11-01T09:32:41+00:00"
         },
         {
-            "name": "phpunit/php-file-iterator",
-            "version": "2.0.3",
+            "name": "php-http/message-factory",
+            "version": "v1.0.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
+                "url": "https://github.com/php-http/message-factory.git",
+                "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
-                "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
+                "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
+                "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "php": ">=5.4",
+                "psr/http-message": "^1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "1.0-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "Http\\Message\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
                 }
             ],
-            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
-            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "description": "Factory interfaces for PSR-7 HTTP Message",
+            "homepage": "http://php-http.org",
             "keywords": [
-                "filesystem",
-                "iterator"
+                "factory",
+                "http",
+                "message",
+                "stream",
+                "uri"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
-                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3"
+                "issues": "https://github.com/php-http/message-factory/issues",
+                "source": "https://github.com/php-http/message-factory/tree/master"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-11-30T08:25:21+00:00"
+            "time": "2015-12-19T14:08:53+00:00"
         },
         {
-            "name": "phpunit/php-text-template",
-            "version": "1.2.1",
+            "name": "php-http/promise",
+            "version": "v1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-text-template.git",
-                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+                "url": "https://github.com/php-http/promise.git",
+                "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
-                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980",
+                "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980",
                 "shasum": ""
             },
-            "require": {
-                "php": ">=5.3.3"
+            "require-dev": {
+                "henrikbjorn/phpspec-code-coverage": "^1.0",
+                "phpspec/phpspec": "^2.4"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "Http\\Promise\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
+                },
+                {
+                    "name": "Joel Wurtz",
+                    "email": "joel.wurtz@gmail.com"
                 }
             ],
-            "description": "Simple template engine.",
-            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "description": "Promise used for asynchronous HTTP requests",
+            "homepage": "http://httplug.io",
             "keywords": [
-                "template"
+                "promise"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
-                "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
+                "issues": "https://github.com/php-http/promise/issues",
+                "source": "https://github.com/php-http/promise/tree/master"
             },
-            "time": "2015-06-21T13:50:34+00:00"
+            "time": "2016-01-26T13:27:02+00:00"
         },
         {
-            "name": "phpunit/php-timer",
-            "version": "2.1.3",
+            "name": "phpdocumentor/reflection-common",
+            "version": "2.2.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
+                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
-                "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "php": "^7.2 || ^8.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.1-dev"
+                    "dev-2.x": "2.x-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "name": "Jaap van Otterdijk",
+                    "email": "opensource@ijaap.nl"
                 }
             ],
-            "description": "Utility class for timing",
-            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+            "homepage": "http://www.phpdoc.org",
             "keywords": [
-                "timer"
+                "FQSEN",
+                "phpDocumentor",
+                "phpdoc",
+                "reflection",
+                "static analysis"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
-                "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
+                "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+                "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-11-30T08:20:02+00:00"
+            "time": "2020-06-27T09:03:43+00:00"
         },
         {
-            "name": "phpunit/php-token-stream",
-            "version": "3.1.2",
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "5.2.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "472b687829041c24b25f475e14c2f38a09edf1c2"
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2",
-                "reference": "472b687829041c24b25f475e14c2f38a09edf1c2",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
+                "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
                 "shasum": ""
             },
             "require": {
-                "ext-tokenizer": "*",
-                "php": ">=7.1"
+                "ext-filter": "*",
+                "php": "^7.2 || ^8.0",
+                "phpdocumentor/reflection-common": "^2.2",
+                "phpdocumentor/type-resolver": "^1.3",
+                "webmozart/assert": "^1.9.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^7.0"
+                "mockery/mockery": "~1.3.2"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "5.x-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": "src"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                },
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "account@ijaap.nl"
                 }
             ],
-            "description": "Wrapper around PHP's tokenizer extension.",
-            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
-            "keywords": [
-                "tokenizer"
-            ],
+            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
-                "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.2"
+                "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "abandoned": true,
-            "time": "2020-11-30T08:38:46+00:00"
+            "time": "2020-09-03T19:13:55+00:00"
         },
         {
-            "name": "phpunit/phpunit",
-            "version": "8.5.17",
+            "name": "phpdocumentor/type-resolver",
+            "version": "1.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "79067856d85421c56d413bd238d4e2cd6b0e54da"
+                "url": "https://github.com/phpDocumentor/TypeResolver.git",
+                "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/79067856d85421c56d413bd238d4e2cd6b0e54da",
-                "reference": "79067856d85421c56d413bd238d4e2cd6b0e54da",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+                "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
                 "shasum": ""
             },
             "require": {
-                "doctrine/instantiator": "^1.3.1",
-                "ext-dom": "*",
-                "ext-json": "*",
-                "ext-libxml": "*",
-                "ext-mbstring": "*",
-                "ext-xml": "*",
-                "ext-xmlwriter": "*",
-                "myclabs/deep-copy": "^1.10.0",
-                "phar-io/manifest": "^2.0.1",
-                "phar-io/version": "^3.0.2",
-                "php": ">=7.2",
-                "phpspec/prophecy": "^1.10.3",
-                "phpunit/php-code-coverage": "^7.0.12",
-                "phpunit/php-file-iterator": "^2.0.2",
-                "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-timer": "^2.1.2",
-                "sebastian/comparator": "^3.0.2",
-                "sebastian/diff": "^3.0.2",
-                "sebastian/environment": "^4.2.3",
-                "sebastian/exporter": "^3.1.2",
-                "sebastian/global-state": "^3.0.0",
-                "sebastian/object-enumerator": "^3.0.3",
-                "sebastian/resource-operations": "^2.0.1",
-                "sebastian/type": "^1.1.3",
-                "sebastian/version": "^2.0.1"
+                "php": "^7.2 || ^8.0",
+                "phpdocumentor/reflection-common": "^2.0"
             },
             "require-dev": {
-                "ext-pdo": "*"
-            },
-            "suggest": {
-                "ext-soap": "*",
-                "ext-xdebug": "*",
-                "phpunit/php-invoker": "^2.0.0"
+                "ext-tokenizer": "*"
             },
-            "bin": [
-                "phpunit"
-            ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "8.5-dev"
+                    "dev-1.x": "1.x-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": "src"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
                 }
             ],
-            "description": "The PHP Unit Testing framework.",
-            "homepage": "https://phpunit.de/",
-            "keywords": [
-                "phpunit",
-                "testing",
-                "xunit"
-            ],
+            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
             "support": {
-                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.17"
+                "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
             },
-            "funding": [
-                {
-                    "url": "https://phpunit.de/donate.html",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2021-06-23T05:12:43+00:00"
+            "time": "2020-09-17T18:55:26+00:00"
         },
         {
-            "name": "psr/event-dispatcher",
-            "version": "1.0.0",
+            "name": "phpspec/prophecy",
+            "version": "1.13.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-fig/event-dispatcher.git",
-                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
-                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
+                "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.0"
+                "doctrine/instantiator": "^1.2",
+                "php": "^7.2 || ~8.0, <8.1",
+                "phpdocumentor/reflection-docblock": "^5.2",
+                "sebastian/comparator": "^3.0 || ^4.0",
+                "sebastian/recursion-context": "^3.0 || ^4.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^6.0",
+                "phpunit/phpunit": "^8.0 || ^9.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.11.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Psr\\EventDispatcher\\": "src/"
+                    "Prophecy\\": "src/Prophecy"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -4826,46 +5007,67 @@
             ],
             "authors": [
                 {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
                 }
             ],
-            "description": "Standard interfaces for event handling.",
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
             "keywords": [
-                "events",
-                "psr",
-                "psr-14"
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
             ],
             "support": {
-                "issues": "https://github.com/php-fig/event-dispatcher/issues",
-                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+                "issues": "https://github.com/phpspec/prophecy/issues",
+                "source": "https://github.com/phpspec/prophecy/tree/1.13.0"
             },
-            "time": "2019-01-08T18:20:26+00:00"
+            "time": "2021-03-17T13:42:18+00:00"
         },
         {
-            "name": "sebastian/code-unit-reverse-lookup",
-            "version": "1.0.2",
+            "name": "phpunit/php-code-coverage",
+            "version": "7.0.14",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
-                "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
-                "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c",
+                "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.6"
+                "ext-dom": "*",
+                "ext-xmlwriter": "*",
+                "php": ">=7.2",
+                "phpunit/php-file-iterator": "^2.0.2",
+                "phpunit/php-text-template": "^1.2.1",
+                "phpunit/php-token-stream": "^3.1.1 || ^4.0",
+                "sebastian/code-unit-reverse-lookup": "^1.0.1",
+                "sebastian/environment": "^4.2.2",
+                "sebastian/version": "^2.0.1",
+                "theseer/tokenizer": "^1.1.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "phpunit/phpunit": "^8.2.2"
+            },
+            "suggest": {
+                "ext-xdebug": "^2.7.2"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "7.0-dev"
                 }
             },
             "autoload": {
@@ -4880,14 +5082,20 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Looks up which function or method a line of code belongs to",
-            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
-                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
+                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14"
             },
             "funding": [
                 {
@@ -4895,26 +5103,24 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T08:15:22+00:00"
+            "time": "2020-12-02T13:39:03+00:00"
         },
         {
-            "name": "sebastian/comparator",
-            "version": "3.0.3",
+            "name": "phpunit/php-file-iterator",
+            "version": "2.0.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
-                "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
+                "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1",
-                "sebastian/diff": "^3.0",
-                "sebastian/exporter": "^3.1"
+                "php": ">=7.1"
             },
             "require-dev": {
                 "phpunit/phpunit": "^8.5"
@@ -4922,7 +5128,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "2.0.x-dev"
                 }
             },
             "autoload": {
@@ -4937,31 +5143,19 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Volker Dusch",
-                    "email": "github@wallbash.com"
-                },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@2bepublished.at"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Provides the functionality to compare PHP values for equality",
-            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
             "keywords": [
-                "comparator",
-                "compare",
-                "equality"
+                "filesystem",
+                "iterator"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/comparator/issues",
-                "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
+                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3"
             },
             "funding": [
                 {
@@ -4969,35 +5163,26 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T08:04:30+00:00"
+            "time": "2020-11-30T08:25:21+00:00"
         },
         {
-            "name": "sebastian/diff",
-            "version": "3.0.3",
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
-                "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^7.5 || ^8.0",
-                "symfony/process": "^2 || ^3.3 || ^4"
+                "php": ">=5.3.3"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.0-dev"
-                }
-            },
             "autoload": {
                 "classmap": [
                     "src/"
@@ -5010,60 +5195,45 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Kore Nordmann",
-                    "email": "mail@kore-nordmann.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Diff implementation",
-            "homepage": "https://github.com/sebastianbergmann/diff",
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
             "keywords": [
-                "diff",
-                "udiff",
-                "unidiff",
-                "unified diff"
+                "template"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/diff/issues",
-                "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
+                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+                "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-11-30T07:59:04+00:00"
+            "time": "2015-06-21T13:50:34+00:00"
         },
         {
-            "name": "sebastian/environment",
-            "version": "4.2.4",
+            "name": "phpunit/php-timer",
+            "version": "2.1.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
-                "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+                "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^7.5"
-            },
-            "suggest": {
-                "ext-posix": "*"
+                "phpunit/phpunit": "^8.5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.2-dev"
+                    "dev-master": "2.1-dev"
                 }
             },
             "autoload": {
@@ -5078,19 +5248,18 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Provides functionality to handle HHVM/PHP environments",
-            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
             "keywords": [
-                "Xdebug",
-                "environment",
-                "hhvm"
+                "timer"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/environment/issues",
-                "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
+                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+                "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
             },
             "funding": [
                 {
@@ -5098,34 +5267,33 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:53:42+00:00"
+            "time": "2020-11-30T08:20:02+00:00"
         },
         {
-            "name": "sebastian/exporter",
-            "version": "3.1.3",
+            "name": "phpunit/php-token-stream",
+            "version": "3.1.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "472b687829041c24b25f475e14c2f38a09edf1c2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
-                "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2",
+                "reference": "472b687829041c24b25f475e14c2f38a09edf1c2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0",
-                "sebastian/recursion-context": "^3.0"
+                "ext-tokenizer": "*",
+                "php": ">=7.1"
             },
             "require-dev": {
-                "ext-mbstring": "*",
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1.x-dev"
+                    "dev-master": "3.1-dev"
                 }
             },
             "autoload": {
@@ -5141,33 +5309,16 @@
                 {
                     "name": "Sebastian Bergmann",
                     "email": "sebastian@phpunit.de"
-                },
-                {
-                    "name": "Jeff Welch",
-                    "email": "whatthejeff@gmail.com"
-                },
-                {
-                    "name": "Volker Dusch",
-                    "email": "github@wallbash.com"
-                },
-                {
-                    "name": "Adam Harvey",
-                    "email": "aharvey@php.net"
-                },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@gmail.com"
                 }
             ],
-            "description": "Provides the functionality to export PHP variables for visualization",
-            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
             "keywords": [
-                "export",
-                "exporter"
+                "tokenizer"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/exporter/issues",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
+                "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
+                "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.2"
             },
             "funding": [
                 {
@@ -5175,38 +5326,65 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:47:53+00:00"
+            "abandoned": true,
+            "time": "2020-11-30T08:38:46+00:00"
         },
         {
-            "name": "sebastian/global-state",
-            "version": "3.0.1",
+            "name": "phpunit/phpunit",
+            "version": "8.5.17",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b"
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "79067856d85421c56d413bd238d4e2cd6b0e54da"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b",
-                "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/79067856d85421c56d413bd238d4e2cd6b0e54da",
+                "reference": "79067856d85421c56d413bd238d4e2cd6b0e54da",
                 "shasum": ""
             },
             "require": {
+                "doctrine/instantiator": "^1.3.1",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-xml": "*",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.10.0",
+                "phar-io/manifest": "^2.0.1",
+                "phar-io/version": "^3.0.2",
                 "php": ">=7.2",
-                "sebastian/object-reflector": "^1.1.1",
-                "sebastian/recursion-context": "^3.0"
+                "phpspec/prophecy": "^1.10.3",
+                "phpunit/php-code-coverage": "^7.0.12",
+                "phpunit/php-file-iterator": "^2.0.2",
+                "phpunit/php-text-template": "^1.2.1",
+                "phpunit/php-timer": "^2.1.2",
+                "sebastian/comparator": "^3.0.2",
+                "sebastian/diff": "^3.0.2",
+                "sebastian/environment": "^4.2.3",
+                "sebastian/exporter": "^3.1.2",
+                "sebastian/global-state": "^3.0.0",
+                "sebastian/object-enumerator": "^3.0.3",
+                "sebastian/resource-operations": "^2.0.1",
+                "sebastian/type": "^1.1.3",
+                "sebastian/version": "^2.0.1"
             },
             "require-dev": {
-                "ext-dom": "*",
-                "phpunit/phpunit": "^8.0"
+                "ext-pdo": "*"
             },
             "suggest": {
-                "ext-uopz": "*"
+                "ext-soap": "*",
+                "ext-xdebug": "*",
+                "phpunit/php-invoker": "^2.0.0"
             },
+            "bin": [
+                "phpunit"
+            ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "8.5-dev"
                 }
             },
             "autoload": {
@@ -5221,107 +5399,107 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Snapshotting of global state",
-            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
             "keywords": [
-                "global state"
+                "phpunit",
+                "testing",
+                "xunit"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1"
+                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.17"
             },
             "funding": [
+                {
+                    "url": "https://phpunit.de/donate.html",
+                    "type": "custom"
+                },
                 {
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:43:24+00:00"
+            "time": "2021-06-23T05:12:43+00:00"
         },
         {
-            "name": "sebastian/object-enumerator",
-            "version": "3.0.4",
+            "name": "psr/event-dispatcher",
+            "version": "1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
-                "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
+                "url": "https://github.com/php-fig/event-dispatcher.git",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
-                "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0",
-                "sebastian/object-reflector": "^1.1.1",
-                "sebastian/recursion-context": "^3.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "php": ">=7.2.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0.x-dev"
+                    "dev-master": "1.0.x-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "src/"
-                ]
+                "psr-4": {
+                    "Psr\\EventDispatcher\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
-            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "description": "Standard interfaces for event handling.",
+            "keywords": [
+                "events",
+                "psr",
+                "psr-14"
+            ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
-                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
+                "issues": "https://github.com/php-fig/event-dispatcher/issues",
+                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
             },
-            "funding": [
-                {
-                    "url": "https://github.com/sebastianbergmann",
-                    "type": "github"
-                }
-            ],
-            "time": "2020-11-30T07:40:27+00:00"
+            "time": "2019-01-08T18:20:26+00:00"
         },
         {
-            "name": "sebastian/object-reflector",
-            "version": "1.1.2",
+            "name": "sebastian/code-unit-reverse-lookup",
+            "version": "1.0.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/object-reflector.git",
-                "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
+                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+                "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
-                "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+                "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0"
+                "php": ">=5.6"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^8.5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1-dev"
+                    "dev-master": "1.0.x-dev"
                 }
             },
             "autoload": {
@@ -5339,11 +5517,11 @@
                     "email": "sebastian@phpunit.de"
                 }
             ],
-            "description": "Allows reflection of object attributes, including inherited and non-public ones",
-            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+            "description": "Looks up which function or method a line of code belongs to",
+            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
             "support": {
-                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
-                "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
+                "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
             },
             "funding": [
                 {
@@ -5351,32 +5529,34 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:37:18+00:00"
+            "time": "2020-11-30T08:15:22+00:00"
         },
         {
-            "name": "sebastian/recursion-context",
-            "version": "3.0.1",
+            "name": "sebastian/comparator",
+            "version": "3.0.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
-                "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
+                "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0"
+                "php": ">=7.1",
+                "sebastian/diff": "^3.0",
+                "sebastian/exporter": "^3.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^8.5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0.x-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
@@ -5398,15 +5578,24 @@
                     "email": "whatthejeff@gmail.com"
                 },
                 {
-                    "name": "Adam Harvey",
-                    "email": "aharvey@php.net"
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
                 }
             ],
-            "description": "Provides functionality to recursively process PHP variables",
-            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-                "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
+                "issues": "https://github.com/sebastianbergmann/comparator/issues",
+                "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
             },
             "funding": [
                 {
@@ -5414,29 +5603,33 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:34:24+00:00"
+            "time": "2020-11-30T08:04:30+00:00"
         },
         {
-            "name": "sebastian/resource-operations",
-            "version": "2.0.2",
+            "name": "sebastian/diff",
+            "version": "3.0.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/resource-operations.git",
-                "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
-                "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+                "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.1"
             },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5 || ^8.0",
+                "symfony/process": "^2 || ^3.3 || ^4"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
@@ -5452,13 +5645,23 @@
                 {
                     "name": "Sebastian Bergmann",
                     "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
                 }
             ],
-            "description": "Provides a list of PHP built-in functions that operate on resources",
-            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
+            ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
-                "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
+                "issues": "https://github.com/sebastianbergmann/diff/issues",
+                "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
             },
             "funding": [
                 {
@@ -5466,32 +5669,35 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:30:19+00:00"
+            "time": "2020-11-30T07:59:04+00:00"
         },
         {
-            "name": "sebastian/type",
-            "version": "1.1.4",
+            "name": "sebastian/environment",
+            "version": "4.2.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/type.git",
-                "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4"
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4",
-                "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+                "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2"
+                "php": ">=7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.2"
+                "phpunit/phpunit": "^7.5"
+            },
+            "suggest": {
+                "ext-posix": "*"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1-dev"
+                    "dev-master": "4.2-dev"
                 }
             },
             "autoload": {
@@ -5506,15 +5712,19 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "email": "sebastian@phpunit.de"
                 }
             ],
-            "description": "Collection of value objects that represent the types of the PHP type system",
-            "homepage": "https://github.com/sebastianbergmann/type",
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/type/issues",
-                "source": "https://github.com/sebastianbergmann/type/tree/1.1.4"
+                "issues": "https://github.com/sebastianbergmann/environment/issues",
+                "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
             },
             "funding": [
                 {
@@ -5522,29 +5732,34 @@
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:25:11+00:00"
+            "time": "2020-11-30T07:53:42+00:00"
         },
         {
-            "name": "sebastian/version",
-            "version": "2.0.1",
+            "name": "sebastian/exporter",
+            "version": "3.1.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/version.git",
-                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
-                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
+                "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.6"
+                "php": ">=7.0",
+                "sebastian/recursion-context": "^3.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "^6.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "3.1.x-dev"
                 }
             },
             "autoload": {
@@ -5559,593 +5774,461 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
                 }
             ],
-            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
-            "homepage": "https://github.com/sebastianbergmann/version",
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/version/issues",
-                "source": "https://github.com/sebastianbergmann/version/tree/master"
+                "issues": "https://github.com/sebastianbergmann/exporter/issues",
+                "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
             },
-            "time": "2016-10-03T07:35:21+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-11-30T07:47:53+00:00"
         },
         {
-            "name": "symfony/console",
-            "version": "v5.3.2",
+            "name": "sebastian/global-state",
+            "version": "3.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/console.git",
-                "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1"
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1",
-                "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b",
+                "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5",
-                "symfony/deprecation-contracts": "^2.1",
-                "symfony/polyfill-mbstring": "~1.0",
-                "symfony/polyfill-php73": "^1.8",
-                "symfony/polyfill-php80": "^1.15",
-                "symfony/service-contracts": "^1.1|^2",
-                "symfony/string": "^5.1"
-            },
-            "conflict": {
-                "symfony/dependency-injection": "<4.4",
-                "symfony/dotenv": "<5.1",
-                "symfony/event-dispatcher": "<4.4",
-                "symfony/lock": "<4.4",
-                "symfony/process": "<4.4"
-            },
-            "provide": {
-                "psr/log-implementation": "1.0"
+                "php": ">=7.2",
+                "sebastian/object-reflector": "^1.1.1",
+                "sebastian/recursion-context": "^3.0"
             },
             "require-dev": {
-                "psr/log": "~1.0",
-                "symfony/config": "^4.4|^5.0",
-                "symfony/dependency-injection": "^4.4|^5.0",
-                "symfony/event-dispatcher": "^4.4|^5.0",
-                "symfony/lock": "^4.4|^5.0",
-                "symfony/process": "^4.4|^5.0",
-                "symfony/var-dumper": "^4.4|^5.0"
+                "ext-dom": "*",
+                "phpunit/phpunit": "^8.0"
             },
             "suggest": {
-                "psr/log": "For using the console logger",
-                "symfony/event-dispatcher": "",
-                "symfony/lock": "",
-                "symfony/process": ""
+                "ext-uopz": "*"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Console\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
+                "classmap": [
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
-            "description": "Eases the creation of beautiful and testable command line interfaces",
-            "homepage": "https://symfony.com",
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
             "keywords": [
-                "cli",
-                "command line",
-                "console",
-                "terminal"
+                "global state"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v5.3.2"
+                "issues": "https://github.com/sebastianbergmann/global-state/issues",
+                "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1"
             },
             "funding": [
                 {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
+                    "url": "https://github.com/sebastianbergmann",
                     "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
                 }
             ],
-            "time": "2021-06-12T09:42:48+00:00"
+            "time": "2020-11-30T07:43:24+00:00"
         },
         {
-            "name": "symfony/css-selector",
-            "version": "v5.3.0",
+            "name": "sebastian/object-enumerator",
+            "version": "3.0.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/css-selector.git",
-                "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814"
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814",
-                "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+                "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5"
+                "php": ">=7.0",
+                "sebastian/object-reflector": "^1.1.1",
+                "sebastian/recursion-context": "^3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.0"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\CssSelector\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
+                "classmap": [
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Jean-François Simon",
-                    "email": "jeanfrancois.simon@sensiolabs.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
-            "description": "Converts CSS selectors to XPath expressions",
-            "homepage": "https://symfony.com",
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
             "support": {
-                "source": "https://github.com/symfony/css-selector/tree/v5.3.0"
+                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
             },
             "funding": [
                 {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
+                    "url": "https://github.com/sebastianbergmann",
                     "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
                 }
             ],
-            "time": "2021-05-26T17:40:38+00:00"
+            "time": "2020-11-30T07:40:27+00:00"
         },
         {
-            "name": "symfony/deprecation-contracts",
-            "version": "v2.4.0",
+            "name": "sebastian/object-reflector",
+            "version": "1.1.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
+                "url": "https://github.com/sebastianbergmann/object-reflector.git",
+                "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
-                "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+                "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "2.4-dev"
-                },
-                "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
+                    "dev-master": "1.1-dev"
                 }
             },
             "autoload": {
-                "files": [
-                    "function.php"
+                "classmap": [
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
-            "description": "A generic function and convention to trigger deprecation notices",
-            "homepage": "https://symfony.com",
+            "description": "Allows reflection of object attributes, including inherited and non-public ones",
+            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
             "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
+                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+                "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
             },
             "funding": [
                 {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
+                    "url": "https://github.com/sebastianbergmann",
                     "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
                 }
             ],
-            "time": "2021-03-23T23:28:01+00:00"
+            "time": "2020-11-30T07:37:18+00:00"
         },
         {
-            "name": "symfony/event-dispatcher",
-            "version": "v5.3.0",
+            "name": "sebastian/recursion-context",
+            "version": "3.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce"
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce",
-                "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
+                "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5",
-                "symfony/deprecation-contracts": "^2.1",
-                "symfony/event-dispatcher-contracts": "^2",
-                "symfony/polyfill-php80": "^1.15"
-            },
-            "conflict": {
-                "symfony/dependency-injection": "<4.4"
-            },
-            "provide": {
-                "psr/event-dispatcher-implementation": "1.0",
-                "symfony/event-dispatcher-implementation": "2.0"
+                "php": ">=7.0"
             },
             "require-dev": {
-                "psr/log": "~1.0",
-                "symfony/config": "^4.4|^5.0",
-                "symfony/dependency-injection": "^4.4|^5.0",
-                "symfony/error-handler": "^4.4|^5.0",
-                "symfony/expression-language": "^4.4|^5.0",
-                "symfony/http-foundation": "^4.4|^5.0",
-                "symfony/service-contracts": "^1.1|^2",
-                "symfony/stopwatch": "^4.4|^5.0"
-            },
-            "suggest": {
-                "symfony/dependency-injection": "",
-                "symfony/http-kernel": ""
+                "phpunit/phpunit": "^6.0"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\EventDispatcher\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
+                "classmap": [
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
                 },
                 {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
                 }
             ],
-            "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
-            "homepage": "https://symfony.com",
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
             "support": {
-                "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0"
+                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
             },
             "funding": [
                 {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
+                    "url": "https://github.com/sebastianbergmann",
                     "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
                 }
             ],
-            "time": "2021-05-26T17:43:10+00:00"
+            "time": "2020-11-30T07:34:24+00:00"
         },
         {
-            "name": "symfony/event-dispatcher-contracts",
-            "version": "v2.4.0",
+            "name": "sebastian/resource-operations",
+            "version": "2.0.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
-                "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11"
+                "url": "https://github.com/sebastianbergmann/resource-operations.git",
+                "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11",
-                "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+                "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5",
-                "psr/event-dispatcher": "^1"
-            },
-            "suggest": {
-                "symfony/event-dispatcher-implementation": ""
+                "php": ">=7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "2.4-dev"
-                },
-                "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Contracts\\EventDispatcher\\": ""
-                }
+                "classmap": [
+                    "src/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
-            "description": "Generic abstractions related to dispatching event",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "abstractions",
-                "contracts",
-                "decoupling",
-                "interfaces",
-                "interoperability",
-                "standards"
-            ],
+            "description": "Provides a list of PHP built-in functions that operate on resources",
+            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
             "support": {
-                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0"
+                "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+                "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
             },
             "funding": [
                 {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
+                    "url": "https://github.com/sebastianbergmann",
                     "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
                 }
             ],
-            "time": "2021-03-23T23:28:01+00:00"
+            "time": "2020-11-30T07:30:19+00:00"
         },
         {
-            "name": "symfony/finder",
-            "version": "v5.3.0",
+            "name": "sebastian/type",
+            "version": "1.1.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/finder.git",
-                "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6"
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6",
-                "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4",
+                "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5"
+                "php": ">=7.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.2"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Finder\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
+                "classmap": [
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Finds files and directories via an intuitive fluent interface",
-            "homepage": "https://symfony.com",
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
             "support": {
-                "source": "https://github.com/symfony/finder/tree/v5.3.0"
+                "issues": "https://github.com/sebastianbergmann/type/issues",
+                "source": "https://github.com/sebastianbergmann/type/tree/1.1.4"
             },
             "funding": [
                 {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
+                    "url": "https://github.com/sebastianbergmann",
                     "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
                 }
             ],
-            "time": "2021-05-26T12:52:38+00:00"
+            "time": "2020-11-30T07:25:11+00:00"
         },
         {
-            "name": "symfony/polyfill-intl-grapheme",
-            "version": "v1.23.0",
+            "name": "sebastian/version",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
-                "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab"
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab",
-                "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
+                "php": ">=5.6"
             },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
+                "classmap": [
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "MIT"
+                "BSD-3-Clause"
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
-            "description": "Symfony polyfill for intl's grapheme_* functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "grapheme",
-                "intl",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0"
+                "issues": "https://github.com/sebastianbergmann/version/issues",
+                "source": "https://github.com/sebastianbergmann/version/tree/master"
             },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-05-27T09:17:38+00:00"
+            "time": "2016-10-03T07:35:21+00:00"
         },
         {
-            "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.23.0",
+            "name": "symfony/css-selector",
+            "version": "v5.3.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
-                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
+                "url": "https://github.com/symfony/css-selector.git",
+                "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
-                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814",
+                "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
+                "php": ">=7.2.5"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                    "Symfony\\Component\\CssSelector\\": ""
                 },
-                "files": [
-                    "bootstrap.php"
-                ],
-                "classmap": [
-                    "Resources/stubs"
+                "exclude-from-classmap": [
+                    "/Tests/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -6154,26 +6237,22 @@
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Jean-François Simon",
+                    "email": "jeanfrancois.simon@sensiolabs.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "description": "Converts CSS selectors to XPath expressions",
             "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "intl",
-                "normalizer",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0"
+                "source": "https://github.com/symfony/css-selector/tree/v5.3.0"
             },
             "funding": [
                 {
@@ -6189,44 +6268,56 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-02-19T12:13:01+00:00"
+            "time": "2021-05-26T17:40:38+00:00"
         },
         {
-            "name": "symfony/polyfill-php73",
-            "version": "v1.23.0",
+            "name": "symfony/event-dispatcher",
+            "version": "v5.3.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-php73.git",
-                "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010"
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010",
-                "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce",
+                "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1",
+                "symfony/event-dispatcher-contracts": "^2",
+                "symfony/polyfill-php80": "^1.15"
             },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
+            "conflict": {
+                "symfony/dependency-injection": "<4.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "2.0"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "^4.4|^5.0",
+                "symfony/dependency-injection": "^4.4|^5.0",
+                "symfony/error-handler": "^4.4|^5.0",
+                "symfony/expression-language": "^4.4|^5.0",
+                "symfony/http-foundation": "^4.4|^5.0",
+                "symfony/service-contracts": "^1.1|^2",
+                "symfony/stopwatch": "^4.4|^5.0"
             },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Php73\\": ""
+                    "Symfony\\Component\\EventDispatcher\\": ""
                 },
-                "files": [
-                    "bootstrap.php"
-                ],
-                "classmap": [
-                    "Resources/stubs"
+                "exclude-from-classmap": [
+                    "/Tests/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -6235,24 +6326,18 @@
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
             "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0"
+                "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0"
             },
             "funding": [
                 {
@@ -6268,33 +6353,33 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-02-19T12:13:01+00:00"
+            "time": "2021-05-26T17:43:10+00:00"
         },
         {
-            "name": "symfony/service-contracts",
-            "version": "v2.2.0",
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/service-contracts.git",
-                "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
-                "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11",
+                "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.2.5",
-                "psr/container": "^1.0"
+                "psr/event-dispatcher": "^1"
             },
             "suggest": {
-                "symfony/service-implementation": ""
+                "symfony/event-dispatcher-implementation": ""
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.2-dev"
+                    "dev-main": "2.4-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
@@ -6303,7 +6388,7 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Contracts\\Service\\": ""
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -6320,7 +6405,7 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Generic abstractions related to writing services",
+            "description": "Generic abstractions related to dispatching event",
             "homepage": "https://symfony.com",
             "keywords": [
                 "abstractions",
@@ -6331,7 +6416,7 @@
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/service-contracts/tree/master"
+                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0"
             },
             "funding": [
                 {
@@ -6347,44 +6432,30 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-09-07T11:33:47+00:00"
+            "time": "2021-03-23T23:28:01+00:00"
         },
         {
-            "name": "symfony/string",
-            "version": "v5.3.2",
+            "name": "symfony/finder",
+            "version": "v5.3.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/string.git",
-                "reference": "0732e97e41c0a590f77e231afc16a327375d50b0"
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0",
-                "reference": "0732e97e41c0a590f77e231afc16a327375d50b0",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6",
+                "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5",
-                "symfony/polyfill-ctype": "~1.8",
-                "symfony/polyfill-intl-grapheme": "~1.0",
-                "symfony/polyfill-intl-normalizer": "~1.0",
-                "symfony/polyfill-mbstring": "~1.0",
-                "symfony/polyfill-php80": "~1.15"
-            },
-            "require-dev": {
-                "symfony/error-handler": "^4.4|^5.0",
-                "symfony/http-client": "^4.4|^5.0",
-                "symfony/translation-contracts": "^1.1|^2",
-                "symfony/var-exporter": "^4.4|^5.0"
+                "php": ">=7.2.5"
             },
             "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Component\\String\\": ""
+                    "Symfony\\Component\\Finder\\": ""
                 },
-                "files": [
-                    "Resources/functions.php"
-                ],
                 "exclude-from-classmap": [
                     "/Tests/"
                 ]
@@ -6395,26 +6466,18 @@
             ],
             "authors": [
                 {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+            "description": "Finds files and directories via an intuitive fluent interface",
             "homepage": "https://symfony.com",
-            "keywords": [
-                "grapheme",
-                "i18n",
-                "string",
-                "unicode",
-                "utf-8",
-                "utf8"
-            ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v5.3.2"
+                "source": "https://github.com/symfony/finder/tree/v5.3.0"
             },
             "funding": [
                 {
@@ -6430,7 +6493,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-06-06T09:51:56+00:00"
+            "time": "2021-05-26T12:52:38+00:00"
         },
         {
             "name": "theseer/tokenizer",
@@ -6623,5 +6686,5 @@
         "ext-mbstring": "*"
     },
     "platform-dev": [],
-    "plugin-api-version": "2.0.0"
+    "plugin-api-version": "2.1.0"
 }
diff --git a/lib/bootstrap.php b/lib/bootstrap.php
index 3230562e9c5d764bd24c8f84fea3dadcd45132ee..a50c4fb3a6e00a34f078b2a128415d1cb4d91d63 100644
--- a/lib/bootstrap.php
+++ b/lib/bootstrap.php
@@ -97,7 +97,7 @@ if (Studip\ENV === 'development' && !in_array('ASSETS_URL', $added) && function_
     }
 }
 
-if (!file_exists($GLOBALS['STUDIP_BASE_PATH'] . '/config/config_local.inc.php')) {
+if (!file_exists($GLOBALS['STUDIP_BASE_PATH'] . '/config/config_local.inc.php') && php_sapi_name() !== 'cli') {
     require_once __DIR__ . '/classes/URLHelper.php';
 
     URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']);
diff --git a/lib/classes/StudipPDO.class.php b/lib/classes/StudipPDO.class.php
index 10738a716444e33b77dd9d001177a0804bb86355..6356222aa03a0438448440d7b59efbc00ee94e6d 100644
--- a/lib/classes/StudipPDO.class.php
+++ b/lib/classes/StudipPDO.class.php
@@ -389,4 +389,14 @@ class StudipPDO extends PDO
         $st->execute($input_parameters);
         return $st->fetchColumn($column);
     }
+
+    /**
+     * Determine if the connected database is a MariaDB database.
+     *
+     * @return bool
+     */
+    public function isMariaDB(): bool
+    {
+        return stripos($this->getAttribute(\PDO::ATTR_SERVER_VERSION), 'MariaDB') !== false;
+    }
 }