Skip to content
Snippets Groups Projects
Commit b0a1a7ad authored by Marcus Eibrink-Lunzenauer's avatar Marcus Eibrink-Lunzenauer Committed by Jan-Hendrik Willms
Browse files

CLI-Skript `studip` einführen und alte Skripte entsprechend umstellen

parent cb0a6759
No related branches found
No related tags found
No related merge requests found
Showing
with 1631 additions and 0 deletions
<?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;
}
}
<?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';
}
}
<?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;
}
}
<?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;
}
}
<?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);
}
}
<?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})";
}
}
<?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.',
];
<?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.',
];
<?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.',
];
<?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',
];
#!/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';
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`
......@@ -31,19 +40,21 @@ 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) {
\DBManager::get()->fetchAll($sql, null, function ($data) use (&$c1, &$c2, $output) {
$c1++;
if (class_exists($data['class'])) {
$rule = new $data['class']($data['rule_id']);
$class_name = '\\' . $data['class'];
if (class_exists($class_name)) {
$rule = new $class_name($data['rule_id']);
if ($rule->getId() === $data['rule_id']) {
echo 'deleting: ' . $rule->getName() . ' with id: ' . $rule->getId() . chr(10);
$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;
}
}
);
printf("found: %s deleted: %s \n", $c1,$c2);
<?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;
}
}
<?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;
}
}
<?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;
}
}
<?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;
}
}
<?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.'
);
}
}
}
<?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;
}
}
}
<?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;
}
}
<?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'
);
}
}
}
<?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);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment