aboutsummaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorMarcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de>2021-12-17 16:20:12 +0000
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2021-12-17 16:20:12 +0000
commitb0a1a7adf5203efa32661b96ecb023fef74c5d2d (patch)
treec4641b27164f1e8d4feb2274164bed26578721af /cli
parentcb0a67594116a17c78182637908c4723f37e7263 (diff)
CLI-Skript `studip` einführen und alte Skripte entsprechend umstellen
Diffstat (limited to 'cli')
-rw-r--r--cli/Commands/AbstractCommand.php72
-rw-r--r--cli/Commands/AbstractPluginCommand.php21
-rw-r--r--cli/Commands/Base/Dump.php51
-rw-r--r--cli/Commands/Checks/Compatibility.php167
-rw-r--r--cli/Commands/Checks/GlobalizedConfig.php148
-rw-r--r--cli/Commands/Checks/HelpTours.php114
-rw-r--r--cli/Commands/Checks/compatibility-rules/studip-4.0.php178
-rw-r--r--cli/Commands/Checks/compatibility-rules/studip-4.2.php17
-rw-r--r--cli/Commands/Checks/compatibility-rules/studip-4.4.php6
-rw-r--r--cli/Commands/Checks/compatibility-rules/studip-5.0.php60
-rw-r--r--cli/Commands/CleanupAdmissionRules.php60
-rw-r--r--cli/Commands/Cronjobs/CronjobExecute.php42
-rw-r--r--cli/Commands/Cronjobs/CronjobList.php39
-rw-r--r--cli/Commands/Cronjobs/CronjobWorker.php24
-rw-r--r--cli/Commands/DB/Dump.php136
-rw-r--r--cli/Commands/DB/MigrateEngine.php148
-rw-r--r--cli/Commands/DB/MigrateFileFormat.php128
-rw-r--r--cli/Commands/Files/Dump.php50
-rw-r--r--cli/Commands/Fix/Biest7789.php114
-rw-r--r--cli/Commands/Fix/Biest7866.php56
-rw-r--r--cli/Commands/Fix/Biest8136.php41
-rw-r--r--cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php40
-rw-r--r--cli/Commands/Fix/IconDimensions.php49
-rw-r--r--cli/Commands/HelpContent/Migrate.php114
-rw-r--r--cli/Commands/Migrate/Migrate.php44
-rw-r--r--cli/Commands/Migrate/MigrateList.php59
-rw-r--r--cli/Commands/Migrate/MigrateStatus.php53
-rw-r--r--cli/Commands/Plugins/I18N/I18NCommand.php77
-rw-r--r--cli/Commands/Plugins/I18N/I18NCompile.php57
-rw-r--r--cli/Commands/Plugins/I18N/I18NDetect.php59
-rw-r--r--cli/Commands/Plugins/I18N/I18NExtract.php74
-rw-r--r--cli/Commands/Plugins/PluginActivate.php39
-rw-r--r--cli/Commands/Plugins/PluginDeactivate.php39
-rw-r--r--cli/Commands/Plugins/PluginInfo.php80
-rw-r--r--cli/Commands/Plugins/PluginInstall.php41
-rw-r--r--cli/Commands/Plugins/PluginListMigrations.php68
-rw-r--r--cli/Commands/Plugins/PluginMigrate.php66
-rw-r--r--cli/Commands/Plugins/PluginRegister.php86
-rw-r--r--cli/Commands/Plugins/PluginScan.php46
-rw-r--r--cli/Commands/Plugins/PluginStatusMigrations.php68
-rw-r--r--cli/Commands/Plugins/PluginUnregister.php51
-rw-r--r--cli/Commands/Resources/UpdateBookingIntervals.php44
-rw-r--r--cli/Commands/SORM/DescribeModels.php285
-rw-r--r--cli/Commands/Translations/VueGettextSplitTranslations.php35
-rw-r--r--cli/Commands/Users/UserDelete.php89
-rwxr-xr-xcli/antelope_to_barracuda.php107
-rwxr-xr-xcli/biest7783-fix.php137
-rwxr-xr-xcli/biest7789-fix.php95
-rwxr-xr-xcli/biest7866-fix.php47
-rwxr-xr-xcli/biest8136-fix.php31
-rwxr-xr-xcli/check-help-tours.php80
-rwxr-xr-xcli/cleanup_admission_rules.php49
-rw-r--r--cli/compatibility-rules/studip-4.0.php179
-rw-r--r--cli/compatibility-rules/studip-4.2.php17
-rw-r--r--cli/compatibility-rules/studip-4.4.php6
-rw-r--r--cli/compatibility-rules/studip-5.0.php60
-rwxr-xr-xcli/create_table_schemes.php47
-rwxr-xr-xcli/cronjob-worker.php34
-rwxr-xr-xcli/cronjobs.php54
-rwxr-xr-xcli/describe_models.php75
-rwxr-xr-xcli/dump_studip.php87
-rwxr-xr-xcli/extract-js-localizations.php204
-rwxr-xr-xcli/fix-icon-dimensions.php22
-rwxr-xr-xcli/fix_collate.php24
-rwxr-xr-xcli/fix_endtime_weekly_recurred_events.php33
-rw-r--r--cli/getopts.php317
-rwxr-xr-xcli/help-translation-tool.php543
-rwxr-xr-xcli/i18n-plugin.php89
-rwxr-xr-xcli/kill_studip_user.php95
-rwxr-xr-xcli/migrate.php69
-rwxr-xr-xcli/migrate_help_content.php89
-rwxr-xr-xcli/myisam_to_innodb.php122
-rwxr-xr-xcli/plugin_manager310
-rwxr-xr-xcli/studip62
-rwxr-xr-xcli/studip-compat.php203
-rw-r--r--cli/studip_cli_env.inc.php2
-rwxr-xr-xcli/tic_5671_scan.php172
-rw-r--r--cli/update-resource-booking-intervals.php33
-rwxr-xr-xcli/vue-gettext-split-translations.php15
79 files changed, 3398 insertions, 3446 deletions
diff --git a/cli/Commands/AbstractCommand.php b/cli/Commands/AbstractCommand.php
new file mode 100644
index 0000000..b3ce3cc
--- /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 0000000..149f54c
--- /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 0000000..39be0a2
--- /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 0000000..4e6cd3e
--- /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 0000000..373534a
--- /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 0000000..04d66c0
--- /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 0000000..0b3fa22
--- /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 0000000..c051d7e
--- /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 0000000..aa4f326
--- /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 0000000..5aec249
--- /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 0000000..3079ae4
--- /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 0000000..23592a1
--- /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 0000000..af4913d
--- /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 0000000..209f112
--- /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 0000000..e2a8a2f
--- /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 0000000..6ae5219
--- /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 0000000..0c97824
--- /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 0000000..6d5c48c
--- /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 0000000..43f0c68
--- /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 0000000..591e79f
--- /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 0000000..17fe249
--- /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 0000000..40e7a2b
--- /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 0000000..13703e9
--- /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 0000000..8ab790a
--- /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 0000000..f5acff8
--- /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 0000000..cff6ce1
--- /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 0000000..e07bbe1
--- /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 0000000..6fa9f77
--- /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 0000000..178152e
--- /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 0000000..584fe00
--- /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 0000000..e1f479f
--- /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 0000000..df57635
--- /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 0000000..637ad7f
--- /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 0000000..492c1ae
--- /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 0000000..1277f4f
--- /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 0000000..2af092f
--- /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 0000000..8bfd036
--- /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 0000000..5167fa3
--- /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 0000000..72b443f
--- /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 0000000..8bc0875
--- /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 0000000..7b08c93
--- /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 0000000..0d01211
--- /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 0000000..3d1256f
--- /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 0000000..d9579f2
--- /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 0000000..2ba854e
--- /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 4360ffd..0000000
--- 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 e3cad62..0000000
--- 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 e94a9f3..0000000
--- 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 ca1f90c..0000000
--- 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 c1bad9a..0000000
--- 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 e028621..0000000
--- 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 49998ff..0000000
--- 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 70475b8..0000000
--- 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 4420e9f..0000000
--- 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 48e5165..0000000
--- 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 af8a70e..0000000
--- 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 a7f557e..0000000
--- 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 15f6e2e..0000000
--- 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 217c664..0000000
--- 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 ebe0db3..0000000
--- 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 52ce0c1..0000000
--- 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 70d14a9..0000000
--- 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 a7e595a..0000000
--- 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 b9b9f67..0000000
--- 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 7e31f29..0000000
--- 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 dde439f..0000000
--- 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 6323eae..0000000
--- 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 e301413..0000000
--- 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 7798fa4..0000000
--- 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 618132a..0000000
--- 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 71b4c66..0000000
--- 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 85dab42..0000000
--- 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 e566c75..0000000
--- 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 0000000..b723ad5
--- /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 18b18c2..0000000
--- 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 a0e33cd..a5ef8f4 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 07fa5c2..0000000
--- 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 50c6e4b..0000000
--- 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 0ff09d1..0000000
--- 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));
-}